Skip to content

Commit 5514edb

Browse files
committed
Load services both from regular jars and files, filter duplicates
1 parent 15b6345 commit 5514edb

File tree

6 files changed

+29
-27
lines changed

6 files changed

+29
-27
lines changed

kotlinx-coroutines-core/build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ kotlin.sourceSets {
4646
jvmTest.dependencies {
4747
api "com.devexperts.lincheck:lincheck:$lincheck_version"
4848
api "com.esotericsoftware:kryo:4.0.0"
49-
5049
implementation project (":android-unit-tests")
5150
}
5251
}

kotlinx-coroutines-core/jvm/src/internal/FastServiceLoader.kt

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
package kotlinx.coroutines.internal
22

3-
import java.util.*
43
import java.io.*
54
import java.net.*
5+
import java.util.*
66
import java.util.jar.*
77
import java.util.zip.*
88

99
/**
1010
* Name of the boolean property that enables using of [FastServiceLoader].
1111
*/
12-
private const val FAST_SERVICE_LOADER_PROPERTY_NAME = "kotlinx.coroutines.verify.service.loader"
12+
private const val FAST_SERVICE_LOADER_PROPERTY_NAME = "kotlinx.coroutines.fast.service.loader"
1313

1414
/**
1515
* A simplified version of [ServiceLoader].
@@ -22,12 +22,10 @@ private const val FAST_SERVICE_LOADER_PROPERTY_NAME = "kotlinx.coroutines.verify
2222
*
2323
* If any error occurs during loading, it fallbacks to [ServiceLoader], mostly to prevent R8 issues.
2424
*/
25-
2625
internal object FastServiceLoader {
2726
private const val PREFIX: String = "META-INF/services/"
2827

29-
@JvmField
30-
internal val FAST_SERVICE_LOADER_ENABLED = systemProp(FAST_SERVICE_LOADER_PROPERTY_NAME, true)
28+
private val FAST_SERVICE_LOADER_ENABLED = systemProp(FAST_SERVICE_LOADER_PROPERTY_NAME, true)
3129

3230
internal fun <S> load(service: Class<S>, loader: ClassLoader): List<S> {
3331
if (!FAST_SERVICE_LOADER_ENABLED) {
@@ -41,16 +39,14 @@ internal object FastServiceLoader {
4139
}
4240
}
4341

42+
// Visible for tests
4443
internal fun <S> loadProviders(service: Class<S>, loader: ClassLoader): List<S> {
4544
val fullServiceName = PREFIX + service.name
46-
val urls = loader.getResources(fullServiceName).toList()
47-
val providers = mutableListOf<S>()
48-
urls.forEach {
49-
val providerNames = parse(it)
50-
providers.addAll(providerNames.map { getProviderInstance(it, loader, service) })
51-
}
45+
// Filter out situations when both JAR and regular files are in the classpath (e.g. IDEA)
46+
val urls = loader.getResources(fullServiceName)
47+
val providers = urls.toList().flatMap { parse(it) }.toSet()
5248
require(providers.isNotEmpty()) { "No providers were loaded with FastServiceLoader" }
53-
return providers
49+
return providers.map { getProviderInstance(it, loader, service) }
5450
}
5551

5652
private fun <S> getProviderInstance(name: String, loader: ClassLoader, service: Class<S>): S {
@@ -60,16 +56,22 @@ internal object FastServiceLoader {
6056
}
6157

6258
private fun parse(url: URL): List<String> {
63-
val string = url.toString()
64-
return if (string.startsWith("jar")) {
65-
val pathToJar = string.substringAfter("jar:file:").substringBefore('!')
66-
val entry = string.substringAfter("!/")
59+
val path = url.toString()
60+
// Fast-path for JARs
61+
if (path.startsWith("jar")) {
62+
val pathToJar = path.substringAfter("jar:file:").substringBefore('!')
63+
val entry = path.substringAfter("!/")
64+
// mind the verify = false flag!
6765
(JarFile(pathToJar, false) as Closeable).use { file ->
68-
BufferedReader(InputStreamReader((file as JarFile).getInputStream(ZipEntry(entry)),"UTF-8")).use { r ->
69-
parseFile(r)
66+
BufferedReader(InputStreamReader((file as JarFile).getInputStream(ZipEntry(entry)), "UTF-8")).use { r ->
67+
return parseFile(r)
7068
}
7169
}
72-
} else emptyList()
70+
}
71+
// Regular path for everything elese
72+
return BufferedReader(InputStreamReader(url.openStream())).use { reader ->
73+
parseFile(reader)
74+
}
7375
}
7476

7577
private fun parseFile(r: BufferedReader): List<String> {

kotlinx-coroutines-core/jvm/test/CommonPoolTest.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ class CommonPoolTest {
3939
val fjp1: ExecutorService = createFJP(1, fjpCtor, dwtfCtor) ?: return
4040
assertTrue(CommonPool.isGoodCommonPool(fjpClass, fjp1))
4141
fjp1.shutdown()
42-
println("CommonPool.isGoodCommonPool test passed")
4342
}
4443

4544
private fun createFJP(

kotlinx-coroutines-core/jvm/test/internal/ServiceLoaderTest.kt renamed to kotlinx-coroutines-core/jvm/test/internal/FastServiceLoaderTest.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@ package kotlinx.coroutines.internal
22

33
import kotlinx.coroutines.CoroutineScope
44
import kotlinx.coroutines.Delay
5-
import kotlin.test.Test
5+
import kotlin.test.*
66

7-
class ServiceLoaderTest {
7+
class FastServiceLoaderTest {
88
@Test
99
fun testLoadingSameModuleService() {
1010
val providers = Delay::class.java.let { FastServiceLoader.loadProviders(it, it.classLoader) }
11-
assert(providers.size == 1 && providers[0].javaClass.name == "kotlinx.coroutines.android.DelayImpl")
11+
assertEquals(1, providers.size)
12+
assertEquals("kotlinx.coroutines.android.DelayImpl", providers[0].javaClass.name)
1213
}
1314

1415
@Test
1516
fun testCrossModuleService() {
1617
val providers = CoroutineScope::class.java.let { FastServiceLoader.loadProviders(it, it.classLoader) }
17-
assert(providers.size == 3)
18+
assertEquals(3, providers.size)
1819
val className = "kotlinx.coroutines.android.EmptyCoroutineScopeImpl"
1920
for (i in 1 .. 3) {
2021
assert(providers[i - 1].javaClass.name == "$className$i")

ui/kotlinx-coroutines-android/android-unit-tests/src/DelayImpl.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ package kotlinx.coroutines.android
22

33
import kotlinx.coroutines.*
44

5-
@InternalCoroutinesApi
5+
// Class for testing service loader
66
class DelayImpl : Delay {
77
override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
88
continuation.cancel()
99
}
10-
}
10+
}

ui/kotlinx-coroutines-android/android-unit-tests/src/EmptyCoroutineScopeImpl.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import kotlinx.coroutines.CoroutineScope
44
import kotlin.coroutines.CoroutineContext
55
import kotlin.coroutines.EmptyCoroutineContext
66

7+
// Classes for testing service loader
78
internal class EmptyCoroutineScopeImpl1 : CoroutineScope {
89
override val coroutineContext: CoroutineContext
910
get() = EmptyCoroutineContext

0 commit comments

Comments
 (0)