Skip to content

Commit 32d9143

Browse files
committed
ConcurrentModificationException from JSONUtil Fix
* Fixes ConcurrentModificationException regression bug reintroduced in 3.8.0 - Corrected test to mimic more realistic case to prevent another regression * Resolves OneSignal#465
1 parent dcd01a4 commit 32d9143

4 files changed

Lines changed: 43 additions & 35 deletions

File tree

OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalPrefs.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class OneSignalPrefs {
5151

5252
static ConcurrentHashMap<String,SharedPreferences> preferencesMap = new ConcurrentHashMap<>();
5353
//use a thread pool executor to execute disk writes
54-
private static final ScheduledThreadPoolExecutor prefsExecutor = new ScheduledThreadPoolExecutor(10);
54+
private static final ScheduledThreadPoolExecutor prefsExecutor = new ScheduledThreadPoolExecutor(1);
5555
static {
5656
prefsExecutor.setThreadFactory(new ThreadFactory() {
5757
@Override

OneSignalSDK/onesignal/src/main/java/com/onesignal/UserStateSynchronizer.java

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class NetworkHandlerThread extends HandlerThread {
4141

4242
Handler mHandler = null;
4343

44-
static final int MAX_RETRIES = 3;
44+
static final int MAX_RETRIES = 3, NETWORK_CALL_DELAY_TO_BUFFER_MS = 5_000;
4545
int currentRetry;
4646

4747
NetworkHandlerThread(int type) {
@@ -52,15 +52,11 @@ class NetworkHandlerThread extends HandlerThread {
5252
}
5353

5454
void runNewJobDelayed() {
55-
currentRetry = 0;
56-
mHandler.removeCallbacksAndMessages(null);
57-
mHandler.postDelayed(getNewRunnable(), 5_000);
58-
}
59-
60-
void runNewJobImmediate() {
61-
currentRetry = 0;
62-
mHandler.removeCallbacksAndMessages(null);
63-
mHandler.post(getNewRunnable());
55+
synchronized (mHandler) {
56+
currentRetry = 0;
57+
mHandler.removeCallbacksAndMessages(null);
58+
mHandler.postDelayed(getNewRunnable(), NETWORK_CALL_DELAY_TO_BUFFER_MS);
59+
}
6460
}
6561

6662
private Runnable getNewRunnable() {
@@ -85,15 +81,17 @@ void stopScheduledRunnable() {
8581
// Retries if not passed limit.
8682
// Returns true if there retrying or there is another future sync scheduled already
8783
boolean doRetry() {
88-
boolean doRetry = currentRetry < MAX_RETRIES;
89-
boolean futureSync = mHandler.hasMessages(0);
84+
synchronized (mHandler) {
85+
boolean doRetry = currentRetry < MAX_RETRIES;
86+
boolean futureSync = mHandler.hasMessages(0);
9087

91-
if (doRetry && !futureSync) {
92-
currentRetry++;
93-
mHandler.postDelayed(getNewRunnable(), currentRetry * 15_000);
94-
}
88+
if (doRetry && !futureSync) {
89+
currentRetry++;
90+
mHandler.postDelayed(getNewRunnable(), currentRetry * 15_000);
91+
}
9592

96-
return mHandler.hasMessages(0);
93+
return mHandler.hasMessages(0);
94+
}
9795
}
9896
}
9997

@@ -169,11 +167,11 @@ private void internalSyncUserState(boolean fromSyncService) {
169167
}
170168

171169
final boolean isSessionCall = isSessionCall();
172-
173-
final JSONObject jsonBody = currentUserState.generateJsonDiff(toSyncUserState, isSessionCall);
174-
final JSONObject dependDiff = generateJsonDiff(currentUserState.dependValues, toSyncUserState.dependValues, null, null);
175-
170+
JSONObject jsonBody, dependDiff;
176171
synchronized (syncLock) {
172+
jsonBody = currentUserState.generateJsonDiff(toSyncUserState, isSessionCall);
173+
dependDiff = generateJsonDiff(currentUserState.dependValues, toSyncUserState.dependValues, null, null);
174+
177175
if (jsonBody == null) {
178176
currentUserState.persistStateAfterSync(dependDiff, null);
179177
return;

OneSignalSDK/unittest/src/test/java/com/onesignal/OneSignalPackagePrivateHelper.java

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88

99
import org.json.JSONArray;
1010
import org.json.JSONObject;
11+
import org.robolectric.shadows.ShadowMessageQueue;
1112
import org.robolectric.util.Scheduler;
1213

1314
import java.lang.reflect.Field;
15+
import java.util.List;
1416
import java.util.Map;
1517
import java.util.Set;
1618

@@ -19,10 +21,10 @@
1921
public class OneSignalPackagePrivateHelper {
2022

2123
private static abstract class RunnableArg<T> {
22-
abstract void run(T object);
24+
abstract void run(T object) throws Exception;
2325
}
2426

25-
static private void processNetworkHandles(RunnableArg runnable) {
27+
static private void processNetworkHandles(RunnableArg runnable) throws Exception {
2628
Set<Map.Entry<Integer, UserStateSynchronizer.NetworkHandlerThread>> entrySet;
2729

2830
entrySet = OneSignalStateSynchronizer.getPushStateSynchronizer().networkHandlerThreads.entrySet();
@@ -35,15 +37,17 @@ static private void processNetworkHandles(RunnableArg runnable) {
3537
}
3638

3739
private static boolean startedRunnable;
38-
public static boolean runAllNetworkRunnables() {
40+
public static boolean runAllNetworkRunnables() throws Exception {
3941
startedRunnable = false;
4042

4143
RunnableArg runnable = new RunnableArg<UserStateSynchronizer.NetworkHandlerThread>() {
4244
@Override
43-
void run(UserStateSynchronizer.NetworkHandlerThread handlerThread) {
44-
Scheduler scheduler = shadowOf(handlerThread.getLooper()).getScheduler();
45-
while (scheduler.runOneTask())
46-
startedRunnable = true;
45+
void run(UserStateSynchronizer.NetworkHandlerThread handlerThread) throws Exception {
46+
synchronized (handlerThread.mHandler) {
47+
Scheduler scheduler = shadowOf(handlerThread.getLooper()).getScheduler();
48+
while (scheduler.runOneTask())
49+
startedRunnable = true;
50+
}
4751
}
4852
};
4953

@@ -92,7 +96,7 @@ public void run() {
9296
return true;
9397
}
9498

95-
public static void resetRunnables() {
99+
public static void resetRunnables() throws Exception {
96100
RunnableArg runnable = new RunnableArg<UserStateSynchronizer.NetworkHandlerThread>() {
97101
@Override
98102
void run(UserStateSynchronizer.NetworkHandlerThread handlerThread) {

OneSignalSDK/unittest/src/test/java/com/test/onesignal/MainOneSignalClassRunner.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1513,22 +1513,28 @@ public void testOneSignalEmptyPendingTaskQueue() throws Exception {
15131513

15141514
private static boolean failedCurModTest;
15151515
@Test
1516+
@Config(sdk = 26)
15161517
public void testSendTagsConcurrentModificationException() throws Exception {
15171518
OneSignalInit();
15181519
threadAndTaskWait();
15191520

1520-
for(int a = 0; a < 10; a++) {
1521-
List<Thread> threadList = new ArrayList<>(30);
1522-
for (int i = 0; i < 30; i++) {
1523-
Thread lastThread = newSendTagTestThread(Thread.currentThread(), i);
1521+
final int TOTAL_RUNS = 75, CONCURRENT_THREADS = 15;
1522+
for(int a = 0; a < TOTAL_RUNS; a++) {
1523+
List<Thread> threadList = new ArrayList<>(CONCURRENT_THREADS);
1524+
for (int i = 0; i < CONCURRENT_THREADS; i++) {
1525+
Thread lastThread = newSendTagTestThread(Thread.currentThread(), a * i);
15241526
lastThread.start();
15251527
threadList.add(lastThread);
15261528
assertFalse(failedCurModTest);
15271529
}
15281530

1531+
OneSignalPackagePrivateHelper.runAllNetworkRunnables();
1532+
15291533
for(Thread thread : threadList)
15301534
thread.join();
1535+
15311536
assertFalse(failedCurModTest);
1537+
System.out.println("Pass " + a + " out of " + TOTAL_RUNS);
15321538
}
15331539
}
15341540

@@ -1541,12 +1547,12 @@ public void run() {
15411547
if (failedCurModTest)
15421548
break;
15431549
OneSignal.sendTags("{\"key" + id + "\": " + i + "}");
1544-
// OneSignalPackagePrivateHelper.OneSignalStateSynchronizer_syncUserState(false);
15451550
}
15461551
} catch (Throwable t) {
15471552
// Ignore the flaky Robolectric null error.
15481553
if (t.getStackTrace()[0].getClassName().equals("org.robolectric.shadows.ShadowMessageQueue"))
15491554
return;
1555+
t.printStackTrace();
15501556
failedCurModTest = true;
15511557
mainThread.interrupt();
15521558
throw t;

0 commit comments

Comments
 (0)