Skip to content

Commit d6e940b

Browse files
[SDK-707] Preserve legacy request execution behavior
1 parent bb2087a commit d6e940b

13 files changed

Lines changed: 547 additions & 75 deletions

‎CHANGELOG.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
55
## [Unreleased]
66

77
### Fixed
8-
- 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, deep-link, and offline operations preserve ordering in isolated serial lanes, while ordinary online API requests retain concurrent execution. Slow work in one lane no longer delays unrelated SDK operations, and client callbacks and attribution updates continue on the main thread.
8+
- 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.
99

1010
## [3.11.0]
1111
### Added

‎iterableapi/src/main/java/com/iterable/iterableapi/IterableApiClient.java‎

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class IterableApiClient {
2626
// A newer push action invalidates retries from earlier registration or disable requests.
2727
private final AtomicLong pushRegistrationRequestGeneration = new AtomicLong();
2828
private RequestProcessor requestProcessor;
29+
private OfflineRequestProcessor offlineRequestProcessor;
2930

3031
interface AuthProvider {
3132
@Nullable
@@ -59,32 +60,50 @@ interface AuthProvider {
5960
new OnlineRequestProcessor(requestDispatchers.push());
6061
}
6162

62-
private RequestProcessor getRequestProcessor() {
63+
private synchronized RequestProcessor getRequestProcessor() {
6364
if (requestProcessor == null) {
6465
requestProcessor = new OnlineRequestProcessor(requestDispatchers.online());
6566
}
6667
return requestProcessor;
6768
}
6869

69-
void setOfflineProcessingEnabled(boolean offlineMode) {
70-
if (offlineMode && this.requestProcessor instanceof OfflineRequestProcessor) {
71-
return;
72-
}
73-
if (!offlineMode && this.requestProcessor instanceof OnlineRequestProcessor) {
70+
synchronized void setOfflineProcessingEnabled(boolean offlineMode) {
71+
if (!offlineMode
72+
&& offlineRequestProcessor == null
73+
&& !hasPendingOfflineRequests()) {
74+
if (!(requestProcessor instanceof OnlineRequestProcessor)) {
75+
requestProcessor = new OnlineRequestProcessor(requestDispatchers.online());
76+
}
7477
return;
7578
}
7679

77-
if (this.requestProcessor instanceof OfflineRequestProcessor) {
78-
((OfflineRequestProcessor) this.requestProcessor).dispose();
80+
// Once needed, keep one offline processor alive while new requests use the
81+
// online processor. Its runner must finish requests queued before offline
82+
// processing was disabled or restored from a previous process.
83+
OfflineRequestProcessor persistentOfflineRequestProcessor =
84+
getOfflineRequestProcessor();
85+
if (offlineMode) {
86+
requestProcessor = persistentOfflineRequestProcessor;
87+
} else if (!(requestProcessor instanceof OnlineRequestProcessor)) {
88+
requestProcessor = new OnlineRequestProcessor(requestDispatchers.online());
7989
}
90+
}
8091

81-
this.requestProcessor = offlineMode
82-
? new OfflineRequestProcessor(
92+
private OfflineRequestProcessor getOfflineRequestProcessor() {
93+
if (offlineRequestProcessor == null) {
94+
offlineRequestProcessor = new OfflineRequestProcessor(
8395
authProvider.getContext(),
84-
requestDispatchers.online(),
85-
requestDispatchers.offline()
86-
)
87-
: new OnlineRequestProcessor(requestDispatchers.online());
96+
requestDispatchers.offlineImmediate(),
97+
requestDispatchers.offlineStored()
98+
);
99+
}
100+
return offlineRequestProcessor;
101+
}
102+
103+
private boolean hasPendingOfflineRequests() {
104+
return IterableTaskStorage
105+
.sharedInstance(authProvider.getContext())
106+
.hasPendingTasks();
88107
}
89108

90109
void getRemoteConfiguration(IterableHelper.IterableActionHandler actionHandler) {
@@ -832,11 +851,30 @@ void sendGetRequest(@NonNull String resourcePath, @NonNull JSONObject json, @Non
832851
getRequestProcessor().processGetRequest(authProvider.getApiKey(), resourcePath, json, authProvider.getAuthToken(), onSuccess, onFailure);
833852
}
834853

835-
void onLogout() {
836-
getRequestProcessor().onLogout(authProvider.getContext());
854+
synchronized void onLogout() {
855+
Context context = authProvider.getContext();
856+
RequestProcessor activeRequestProcessor = getRequestProcessor();
857+
if (offlineRequestProcessor != null) {
858+
offlineRequestProcessor.onLogout(context);
859+
} else {
860+
IterableTaskStorage.sharedInstance(context).deleteAllTasks();
861+
}
862+
if (activeRequestProcessor != offlineRequestProcessor) {
863+
activeRequestProcessor.onLogout(context);
864+
}
837865
authProvider.resetAuth();
838866
}
839867

868+
synchronized void dispose() {
869+
if (offlineRequestProcessor != null) {
870+
if (requestProcessor == offlineRequestProcessor) {
871+
requestProcessor = null;
872+
}
873+
offlineRequestProcessor.dispose();
874+
offlineRequestProcessor = null;
875+
}
876+
}
877+
840878
void mergeUser(String sourceEmail, String sourceUserId, String destinationEmail, String destinationUserId, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) {
841879
JSONObject requestJson = new JSONObject();
842880
try {

‎iterableapi/src/main/java/com/iterable/iterableapi/IterableExecutors.java‎

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.util.concurrent.ArrayBlockingQueue;
88
import java.util.concurrent.Executor;
99
import java.util.concurrent.Executors;
10+
import java.util.concurrent.LinkedBlockingQueue;
1011
import java.util.concurrent.ThreadPoolExecutor;
1112
import java.util.concurrent.TimeUnit;
1213
import java.util.concurrent.atomic.AtomicInteger;
@@ -15,19 +16,32 @@ final class IterableExecutors {
1516
// HttpURLConnection is blocking I/O, so ordinary API work uses a bounded
1617
// multi-thread pool rather than the serial executors used for ordered work.
1718
static final int REQUEST_THREAD_COUNT = 8;
18-
// Matches the historical AsyncTask queue bound without inheriting its
19+
// Bounds the primary pool without inheriting AsyncTask's
1920
// platform-version-dependent thread-pool behavior.
2021
static final int REQUEST_QUEUE_CAPACITY = 128;
2122
private static final long REQUEST_THREAD_KEEP_ALIVE_SECONDS = 30;
23+
private static final int REQUEST_OVERFLOW_THREAD_COUNT = 5;
24+
private static final long REQUEST_OVERFLOW_KEEP_ALIVE_SECONDS = 3;
2225
private static final AtomicInteger REQUEST_THREAD_ID = new AtomicInteger();
26+
private static final AtomicInteger REQUEST_OVERFLOW_THREAD_ID = new AtomicInteger();
2327
private static final Executor PUSH_EXECUTOR =
2428
newSingleThreadExecutor("IterablePushExecutor");
2529
private static final Executor DEEP_LINK_EXECUTOR =
2630
newSingleThreadExecutor("IterableDeepLinkExecutor");
27-
private static final Executor OFFLINE_EXECUTOR =
28-
newSingleThreadExecutor("IterableOfflineExecutor");
31+
private static final Executor SERIAL_REQUEST_EXECUTOR =
32+
newSingleThreadExecutor("IterableSerialRequestExecutor");
33+
private static final Executor OFFLINE_STORED_EXECUTOR =
34+
newSingleThreadExecutor("IterableOfflineStoredExecutor");
35+
// AsyncTask's concurrent executor used a backup queue instead of dropping
36+
// work when its primary pool was saturated. Preserve that delivery behavior.
37+
private static final Executor REQUEST_OVERFLOW_EXECUTOR =
38+
newRequestOverflowExecutor(REQUEST_OVERFLOW_THREAD_COUNT);
2939
private static final Executor REQUEST_EXECUTOR =
30-
newRequestExecutor(REQUEST_THREAD_COUNT, REQUEST_QUEUE_CAPACITY);
40+
newRequestExecutor(
41+
REQUEST_THREAD_COUNT,
42+
REQUEST_QUEUE_CAPACITY,
43+
REQUEST_OVERFLOW_EXECUTOR
44+
);
3145
private static final Executor MAIN_EXECUTOR =
3246
runnable -> new Handler(Looper.getMainLooper()).post(runnable);
3347

@@ -46,8 +60,16 @@ static Executor request() {
4660
return REQUEST_EXECUTOR;
4761
}
4862

49-
static Executor offline() {
50-
return OFFLINE_EXECUTOR;
63+
static Executor offlineImmediate() {
64+
return SERIAL_REQUEST_EXECUTOR;
65+
}
66+
67+
static Executor requestRetry() {
68+
return SERIAL_REQUEST_EXECUTOR;
69+
}
70+
71+
static Executor offlineStored() {
72+
return OFFLINE_STORED_EXECUTOR;
5173
}
5274

5375
static Executor main() {
@@ -61,6 +83,14 @@ private static Executor newSingleThreadExecutor(String threadName) {
6183
}
6284

6385
static ThreadPoolExecutor newRequestExecutor(int threadCount, int queueCapacity) {
86+
return newRequestExecutor(threadCount, queueCapacity, REQUEST_OVERFLOW_EXECUTOR);
87+
}
88+
89+
static ThreadPoolExecutor newRequestExecutor(
90+
int threadCount,
91+
int queueCapacity,
92+
Executor overflowExecutor
93+
) {
6494
ThreadPoolExecutor executor = new ThreadPoolExecutor(
6595
threadCount,
6696
threadCount,
@@ -71,7 +101,24 @@ static ThreadPoolExecutor newRequestExecutor(int threadCount, int queueCapacity)
71101
runnable,
72102
"IterableRequestExecutor-" + REQUEST_THREAD_ID.incrementAndGet()
73103
),
74-
new ThreadPoolExecutor.AbortPolicy()
104+
(runnable, ignored) -> overflowExecutor.execute(runnable)
105+
);
106+
executor.allowCoreThreadTimeOut(true);
107+
return executor;
108+
}
109+
110+
static ThreadPoolExecutor newRequestOverflowExecutor(int threadCount) {
111+
ThreadPoolExecutor executor = new ThreadPoolExecutor(
112+
threadCount,
113+
threadCount,
114+
REQUEST_OVERFLOW_KEEP_ALIVE_SECONDS,
115+
TimeUnit.SECONDS,
116+
new LinkedBlockingQueue<>(),
117+
runnable -> newThread(
118+
runnable,
119+
"IterableRequestOverflowExecutor-"
120+
+ REQUEST_OVERFLOW_THREAD_ID.incrementAndGet()
121+
)
75122
);
76123
executor.allowCoreThreadTimeOut(true);
77124
return executor;

0 commit comments

Comments
 (0)