@@ -9,6 +9,7 @@ import com.onesignal.mocks.MockPreferencesService
99import com.onesignal.user.internal.subscriptions.SubscriptionModel
1010import com.onesignal.user.internal.subscriptions.SubscriptionModelStore
1111import io.kotest.core.spec.style.FunSpec
12+ import io.kotest.matchers.shouldBe
1213import io.kotest.runner.junit4.KotestTestRunner
1314import junit.framework.TestCase
1415import org.junit.runner.RunWith
@@ -70,13 +71,11 @@ class ModelingTests : FunSpec({
7071 val t1 =
7172 Thread {
7273 // acquire "ModelStore.models", then trigger the onChanged event
73- System .out .println("1")
7474 modelStore.add(newSubscriptionModel)
7575 }
7676
7777 val t2 =
7878 Thread {
79- System .out .println("2")
8079 // acquire "model.data", then wait for "ModelStore.models"
8180 newSubscriptionModel.toJSON()
8281 }
@@ -116,4 +115,29 @@ class ModelingTests : FunSpec({
116115 // verify if the thread has been successfully terminated
117116 TestCase .assertEquals(Thread .State .TERMINATED , t2.state)
118117 }
118+
119+ test("Unsubscribing handler in change event may cause the concurrent modification exception") {
120+ // Given an arbitrary model
121+ val modelStore = MockHelper .configModelStore()
122+ val model = modelStore.model
123+
124+ // subscribe to a change handler
125+ model.subscribe(
126+ object : IModelChangedHandler {
127+ override fun onChanged(
128+ args: ModelChangedArgs ,
129+ tag: String ,
130+ ) {
131+ // remove from "subscribers" while "subscribers" is being accessed
132+ model.unsubscribe(this)
133+ }
134+ },
135+ )
136+
137+ // this will trigger EventProducer.fire and loop through the list "subscribers"
138+ model.setOptAnyProperty("key1", "value1")
139+
140+ // ensure no concurrent modification exception is thrown and "subcribers" is clear
141+ model.hasSubscribers shouldBe false
142+ }
119143})
0 commit comments