Skip to content

Commit b8a2ba0

Browse files
authored
Merge pull request #2012 from OneSignal/fix/service-provide-thread-safety
Make ServiceBuilder.getService thread safe
2 parents 221a02e + 3932338 commit b8a2ba0

File tree

2 files changed

+63
-20
lines changed

2 files changed

+63
-20
lines changed

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/services/ServiceProvider.kt

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,9 @@ import com.onesignal.debug.internal.logging.Logging
88
class ServiceProvider(
99
registrations: List<ServiceRegistration<*>>,
1010
) : IServiceProvider {
11-
private var serviceMap: Map<Class<*>, List<ServiceRegistration<*>>>
11+
private val serviceMap = mutableMapOf<Class<*>, MutableList<ServiceRegistration<*>>>()
1212

1313
init {
14-
val serviceMap = mutableMapOf<Class<*>, MutableList<ServiceRegistration<*>>>()
15-
1614
// go through the registrations to create the service map for easier lookup post-build
1715
for (reg in registrations) {
1816
for (service in reg.services) {
@@ -23,8 +21,6 @@ class ServiceProvider(
2321
}
2422
}
2523
}
26-
27-
this.serviceMap = serviceMap
2824
}
2925

3026
internal inline fun <reified T : Any> hasService(): Boolean {
@@ -44,23 +40,27 @@ class ServiceProvider(
4440
}
4541

4642
override fun <T> hasService(c: Class<T>): Boolean {
47-
return serviceMap.containsKey(c)
43+
synchronized(serviceMap) {
44+
return serviceMap.containsKey(c)
45+
}
4846
}
4947

5048
override fun <T> getAllServices(c: Class<T>): List<T> {
51-
val listOfServices: MutableList<T> = mutableListOf()
49+
synchronized(serviceMap) {
50+
val listOfServices: MutableList<T> = mutableListOf()
5251

53-
if (serviceMap.containsKey(c)) {
54-
for (serviceReg in serviceMap!![c]!!) {
55-
val service =
56-
serviceReg.resolve(this) as T?
57-
?: throw Exception("Could not instantiate service: $serviceReg")
52+
if (serviceMap.containsKey(c)) {
53+
for (serviceReg in serviceMap!![c]!!) {
54+
val service =
55+
serviceReg.resolve(this) as T?
56+
?: throw Exception("Could not instantiate service: $serviceReg")
5857

59-
listOfServices.add(service)
58+
listOfServices.add(service)
59+
}
6060
}
61-
}
6261

63-
return listOfServices
62+
return listOfServices
63+
}
6464
}
6565

6666
override fun <T> getService(c: Class<T>): T {
@@ -74,11 +74,10 @@ class ServiceProvider(
7474
}
7575

7676
override fun <T> getServiceOrNull(c: Class<T>): T? {
77-
Logging.debug("${indent}Retrieving service $c")
78-
// indent += " "
79-
val service = serviceMap[c]?.last()?.resolve(this) as T?
80-
// indent = indent.substring(0, indent.length-2)
81-
return service
77+
synchronized(serviceMap) {
78+
Logging.debug("${indent}Retrieving service $c")
79+
return serviceMap[c]?.last()?.resolve(this) as T?
80+
}
8281
}
8382

8483
companion object {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.onesignal.common
2+
3+
import com.onesignal.common.services.ServiceBuilder
4+
import com.onesignal.common.services.ServiceProvider
5+
import io.kotest.core.spec.style.FunSpec
6+
import io.kotest.matchers.types.shouldBeSameInstanceAs
7+
import java.util.concurrent.LinkedBlockingQueue
8+
9+
internal interface IMyTestInterface
10+
11+
internal class MySlowConstructorClass : IMyTestInterface {
12+
init {
13+
// NOTE: Keep these println calls, otherwise Kotlin optimizes
14+
// something which cases the test not fail when it should.
15+
println("MySlowConstructorClass BEFORE")
16+
Thread.sleep(10)
17+
println("MySlowConstructorClass AFTER")
18+
}
19+
}
20+
21+
class ServiceProviderTest : FunSpec({
22+
23+
fun setupServiceProviderWithSlowInitClass(): ServiceProvider {
24+
val serviceBuilder = ServiceBuilder()
25+
serviceBuilder.register<MySlowConstructorClass>().provides<IMyTestInterface>()
26+
return serviceBuilder.build()
27+
}
28+
29+
test("getService is thread safe") {
30+
val services = setupServiceProviderWithSlowInitClass()
31+
32+
val queue = LinkedBlockingQueue<IMyTestInterface>()
33+
Thread {
34+
queue.add(services.getService<IMyTestInterface>())
35+
}.start()
36+
Thread {
37+
queue.add(services.getService<IMyTestInterface>())
38+
}.start()
39+
40+
val firstReference = queue.take()
41+
val secondReference = queue.take()
42+
firstReference shouldBeSameInstanceAs secondReference
43+
}
44+
})

0 commit comments

Comments
 (0)