Skip to content

Commit d8abaee

Browse files
authored
Merge pull request #1876 from OneSignal/fix/concurrent_modification_exception
Fix: Add synchronized blocks to prevent ConcurrentModificationException
2 parents ca26745 + ae758cf commit d8abaee

File tree

4 files changed

+155
-108
lines changed

4 files changed

+155
-108
lines changed

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/events/EventProducer.kt

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,25 @@ open class EventProducer<THandler> : IEventNotifier<THandler> {
1616
private val subscribers: MutableList<THandler> = Collections.synchronizedList(mutableListOf())
1717

1818
override fun subscribe(handler: THandler) {
19-
subscribers.add(handler)
19+
synchronized(subscribers) {
20+
subscribers.add(handler)
21+
}
2022
}
2123

2224
override fun unsubscribe(handler: THandler) {
23-
subscribers.remove(handler)
25+
synchronized(subscribers) {
26+
subscribers.remove(handler)
27+
}
2428
}
2529

2630
/**
2731
* Subscribe all from an existing producer to this subscriber.
2832
*/
2933
fun subscribeAll(from: EventProducer<THandler>) {
30-
for (s in from.subscribers) {
31-
subscribe(s)
34+
synchronized(subscribers) {
35+
for (s in from.subscribers) {
36+
subscribe(s)
37+
}
3238
}
3339
}
3440

@@ -39,8 +45,10 @@ open class EventProducer<THandler> : IEventNotifier<THandler> {
3945
* @param callback The callback will be invoked for each subscribed handler, allowing you to call the handler.
4046
*/
4147
fun fire(callback: (THandler) -> Unit) {
42-
for (s in subscribers) {
43-
callback(s)
48+
synchronized(subscribers) {
49+
for (s in subscribers) {
50+
callback(s)
51+
}
4452
}
4553
}
4654

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/Model.kt

Lines changed: 51 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ open class Model(
5151
* specified, must also specify [_parentModel]
5252
*/
5353
private val _parentProperty: String? = null,
54+
private val initializationLock: Any = Any(),
5455
) : IEventNotifier<IModelChangedHandler> {
5556
/**
5657
* A unique identifier for this model.
@@ -123,19 +124,25 @@ open class Model(
123124
id: String?,
124125
model: Model,
125126
) {
126-
data.clear()
127+
val newData = Collections.synchronizedMap(mutableMapOf<String, Any?>())
128+
127129
for (item in model.data) {
128130
if (item.value is Model) {
129131
val childModel = item.value as Model
130132
childModel._parentModel = this
131-
data[item.key] = childModel
133+
newData[item.key] = childModel
132134
} else {
133-
data[item.key] = item.value
135+
newData[item.key] = item.value
134136
}
135137
}
136138

137139
if (id != null) {
138-
data[::id.name] = id
140+
newData[::id.name] = id
141+
}
142+
143+
synchronized(initializationLock) {
144+
data.clear()
145+
data.putAll(newData)
139146
}
140147
}
141148

@@ -429,19 +436,21 @@ open class Model(
429436
tag: String = ModelChangeTags.NORMAL,
430437
forceChange: Boolean = false,
431438
) {
432-
val oldValue = data[name]
439+
synchronized(data) {
440+
val oldValue = data[name]
433441

434-
if (oldValue == value && !forceChange) {
435-
return
436-
}
442+
if (oldValue == value && !forceChange) {
443+
return
444+
}
437445

438-
if (value != null) {
439-
data[name] = value
440-
} else if (data.containsKey(name)) {
441-
data.remove(name)
442-
}
446+
if (value != null) {
447+
data[name] = value
448+
} else if (data.containsKey(name)) {
449+
data.remove(name)
450+
}
443451

444-
notifyChanged(name, name, tag, oldValue, value)
452+
notifyChanged(name, name, tag, oldValue, value)
453+
}
445454
}
446455

447456
/**
@@ -627,12 +636,14 @@ open class Model(
627636
name: String,
628637
create: (() -> Any?)? = null,
629638
): Any? {
630-
return if (data.containsKey(name) || create == null) {
631-
data[name]
632-
} else {
633-
val defaultValue = create()
634-
data[name] = defaultValue as Any?
635-
defaultValue
639+
synchronized(data) {
640+
return if (data.containsKey(name) || create == null) {
641+
data[name]
642+
} else {
643+
val defaultValue = create()
644+
data[name] = defaultValue as Any?
645+
defaultValue
646+
}
636647
}
637648
}
638649

@@ -660,29 +671,31 @@ open class Model(
660671
* @return The resulting [JSONObject].
661672
*/
662673
fun toJSON(): JSONObject {
663-
val jsonObject = JSONObject()
664-
for (kvp in data) {
665-
when (val value = kvp.value) {
666-
is Model -> {
667-
jsonObject.put(kvp.key, value.toJSON())
668-
}
669-
is List<*> -> {
670-
val jsonArray = JSONArray()
671-
for (arrayItem in value) {
672-
if (arrayItem is Model) {
673-
jsonArray.put(arrayItem.toJSON())
674-
} else {
675-
jsonArray.put(arrayItem)
674+
synchronized(initializationLock) {
675+
val jsonObject = JSONObject()
676+
for (kvp in data) {
677+
when (val value = kvp.value) {
678+
is Model -> {
679+
jsonObject.put(kvp.key, value.toJSON())
680+
}
681+
is List<*> -> {
682+
val jsonArray = JSONArray()
683+
for (arrayItem in value) {
684+
if (arrayItem is Model) {
685+
jsonArray.put(arrayItem.toJSON())
686+
} else {
687+
jsonArray.put(arrayItem)
688+
}
676689
}
690+
jsonObject.put(kvp.key, jsonArray)
691+
}
692+
else -> {
693+
jsonObject.put(kvp.key, value)
677694
}
678-
jsonObject.put(kvp.key, jsonArray)
679-
}
680-
else -> {
681-
jsonObject.put(kvp.key, value)
682695
}
683696
}
697+
return jsonObject
684698
}
685-
return jsonObject
686699
}
687700

688701
override fun subscribe(handler: IModelChangedHandler) = changeNotifier.subscribe(handler)

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/modeling/ModelStore.kt

Lines changed: 72 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -40,25 +40,29 @@ abstract class ModelStore<TModel>(
4040
model: TModel,
4141
tag: String,
4242
) {
43-
val oldModel = models.firstOrNull { it.id == model.id }
44-
if (oldModel != null) {
45-
removeItem(oldModel, tag)
46-
}
43+
synchronized(models) {
44+
val oldModel = models.firstOrNull { it.id == model.id }
45+
if (oldModel != null) {
46+
removeItem(oldModel, tag)
47+
}
4748

48-
addItem(model, tag)
49+
addItem(model, tag)
50+
}
4951
}
5052

5153
override fun add(
5254
index: Int,
5355
model: TModel,
5456
tag: String,
5557
) {
56-
val oldModel = models.firstOrNull { it.id == model.id }
57-
if (oldModel != null) {
58-
removeItem(oldModel, tag)
59-
}
58+
synchronized(models) {
59+
val oldModel = models.firstOrNull { it.id == model.id }
60+
if (oldModel != null) {
61+
removeItem(oldModel, tag)
62+
}
6063

61-
addItem(model, tag, index)
64+
addItem(model, tag, index)
65+
}
6266
}
6367

6468
override fun list(): Collection<TModel> {
@@ -73,40 +77,48 @@ abstract class ModelStore<TModel>(
7377
id: String,
7478
tag: String,
7579
) {
76-
val model = models.firstOrNull { it.id == id } ?: return
77-
removeItem(model, tag)
80+
synchronized(models) {
81+
val model = models.firstOrNull { it.id == id } ?: return
82+
removeItem(model, tag)
83+
}
7884
}
7985

8086
override fun onChanged(
8187
args: ModelChangedArgs,
8288
tag: String,
8389
) {
84-
persist()
90+
synchronized(models) {
91+
persist()
8592

86-
changeSubscription.fire { it.onModelUpdated(args, tag) }
93+
changeSubscription.fire { it.onModelUpdated(args, tag) }
94+
}
8795
}
8896

8997
override fun replaceAll(
9098
models: List<TModel>,
9199
tag: String,
92100
) {
93-
clear(tag)
101+
synchronized(models) {
102+
clear(tag)
94103

95-
for (model in models) {
96-
add(model, tag)
104+
for (model in models) {
105+
add(model, tag)
106+
}
97107
}
98108
}
99109

100110
override fun clear(tag: String) {
101-
val localList = models.toList()
102-
models.clear()
111+
synchronized(models) {
112+
val localList = models.toList()
113+
models.clear()
103114

104-
persist()
115+
persist()
105116

106-
for (item in localList) {
107-
// no longer listen for changes to this model
108-
item.unsubscribe(this)
109-
changeSubscription.fire { it.onModelRemoved(item, tag) }
117+
for (item in localList) {
118+
// no longer listen for changes to this model
119+
item.unsubscribe(this)
120+
changeSubscription.fire { it.onModelRemoved(item, tag) }
121+
}
110122
}
111123
}
112124

@@ -115,55 +127,63 @@ abstract class ModelStore<TModel>(
115127
tag: String,
116128
index: Int? = null,
117129
) {
118-
if (index != null) {
119-
models.add(index, model)
120-
} else {
121-
models.add(model)
122-
}
130+
synchronized(models) {
131+
if (index != null) {
132+
models.add(index, model)
133+
} else {
134+
models.add(model)
135+
}
123136

124-
// listen for changes to this model
125-
model.subscribe(this)
137+
// listen for changes to this model
138+
model.subscribe(this)
126139

127-
persist()
140+
persist()
128141

129-
changeSubscription.fire { it.onModelAdded(model, tag) }
142+
changeSubscription.fire { it.onModelAdded(model, tag) }
143+
}
130144
}
131145

132146
private fun removeItem(
133147
model: TModel,
134148
tag: String,
135149
) {
136-
models.remove(model)
150+
synchronized(models) {
151+
models.remove(model)
137152

138-
// no longer listen for changes to this model
139-
model.unsubscribe(this)
153+
// no longer listen for changes to this model
154+
model.unsubscribe(this)
140155

141-
persist()
156+
persist()
142157

143-
changeSubscription.fire { it.onModelRemoved(model, tag) }
158+
changeSubscription.fire { it.onModelRemoved(model, tag) }
159+
}
144160
}
145161

146162
protected fun load() {
147-
if (name != null && _prefs != null) {
148-
val str = _prefs.getString(PreferenceStores.ONESIGNAL, PreferenceOneSignalKeys.MODEL_STORE_PREFIX + name, "[]")
149-
val jsonArray = JSONArray(str)
150-
for (index in 0 until jsonArray.length()) {
151-
val newModel = create(jsonArray.getJSONObject(index)) ?: continue
152-
models.add(newModel)
153-
// listen for changes to this model
154-
newModel.subscribe(this)
163+
synchronized(models) {
164+
if (name != null && _prefs != null) {
165+
val str = _prefs.getString(PreferenceStores.ONESIGNAL, PreferenceOneSignalKeys.MODEL_STORE_PREFIX + name, "[]")
166+
val jsonArray = JSONArray(str)
167+
for (index in 0 until jsonArray.length()) {
168+
val newModel = create(jsonArray.getJSONObject(index)) ?: continue
169+
models.add(newModel)
170+
// listen for changes to this model
171+
newModel.subscribe(this)
172+
}
155173
}
156174
}
157175
}
158176

159177
fun persist() {
160-
if (name != null && _prefs != null) {
161-
val jsonArray = JSONArray()
162-
for (model in models) {
163-
jsonArray.put(model.toJSON())
178+
synchronized(models) {
179+
if (name != null && _prefs != null) {
180+
val jsonArray = JSONArray()
181+
for (model in models) {
182+
jsonArray.put(model.toJSON())
183+
}
184+
185+
_prefs.saveString(PreferenceStores.ONESIGNAL, PreferenceOneSignalKeys.MODEL_STORE_PREFIX + name, jsonArray.toString())
164186
}
165-
166-
_prefs.saveString(PreferenceStores.ONESIGNAL, PreferenceOneSignalKeys.MODEL_STORE_PREFIX + name, jsonArray.toString())
167187
}
168188
}
169189

0 commit comments

Comments
 (0)