-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Custom ServiceLoader without jar checksum verification
Fixes #878
- Loading branch information
1 parent
8273a75
commit d8aa178
Showing
15 changed files
with
168 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
#Fri Mar 15 12:06:46 CET 2019 | ||
distributionBase=GRADLE_USER_HOME | ||
distributionPath=wrapper/dists | ||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-bin.zip | ||
zipStoreBase=GRADLE_USER_HOME | ||
zipStorePath=wrapper/dists | ||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
kotlinx-coroutines-core/jvm/resources/META-INF/services/kotlinx.coroutines.CoroutineScope
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
kotlinx.coroutines.android.EmptyCoroutineScopeImpl1 | ||
kotlinx.coroutines.android.EmptyCoroutineScopeImpl2 | ||
# testing configuration file parsing # kotlinx.coroutines.service.loader.LocalEmptyCoroutineScope2 | ||
|
||
kotlinx.coroutines.android.EmptyCoroutineScopeImpl2 | ||
|
||
kotlinx.coroutines.android.EmptyCoroutineScopeImpl1 | ||
|
||
|
||
kotlinx.coroutines.android.EmptyCoroutineScopeImpl1 | ||
|
||
|
||
kotlinx.coroutines.android.EmptyCoroutineScopeImpl3#comment |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
87 changes: 87 additions & 0 deletions
87
kotlinx-coroutines-core/jvm/src/internal/FastServiceLoader.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package kotlinx.coroutines.internal | ||
|
||
import java.util.* | ||
import java.io.* | ||
import java.net.* | ||
import java.util.jar.* | ||
import java.util.zip.* | ||
|
||
/** | ||
* Name of the boolean property that enables using of [FastServiceLoader]. | ||
*/ | ||
private const val FAST_SERVICE_LOADER_PROPERTY_NAME = "kotlinx.coroutines.verify.service.loader" | ||
|
||
/** | ||
* A simplified version of [ServiceLoader]. | ||
* FastServiceLoader locates and instantiates all service providers named in configuration | ||
* files placed in the resource directory <tt>META-INF/services</tt>. | ||
* | ||
* The main difference between this class and classic service loader is in skipping | ||
* verification JARs. A verification requires reading the whole JAR (and it causes problems and ANRs on Android devices) | ||
* and prevents only trivial checksum issues. See #878. | ||
* | ||
* If any error occurs during loading, it fallbacks to [ServiceLoader], mostly to prevent R8 issues. | ||
*/ | ||
|
||
internal object FastServiceLoader { | ||
private const val PREFIX: String = "META-INF/services/" | ||
|
||
@JvmField | ||
internal val FAST_SERVICE_LOADER_ENABLED = systemProp(FAST_SERVICE_LOADER_PROPERTY_NAME, true) | ||
|
||
internal fun <S> load(service: Class<S>, loader: ClassLoader): List<S> { | ||
if (!FAST_SERVICE_LOADER_ENABLED) { | ||
return ServiceLoader.load(service, loader).toList() | ||
} | ||
return try { | ||
loadProviders(service, loader) | ||
} catch (e: Throwable) { | ||
// Fallback to default service loader | ||
ServiceLoader.load(service, loader).toList() | ||
} | ||
} | ||
|
||
internal fun <S> loadProviders(service: Class<S>, loader: ClassLoader): List<S> { | ||
val fullServiceName = PREFIX + service.name | ||
val urls = loader.getResources(fullServiceName).toList() | ||
val providers = mutableListOf<S>() | ||
urls.forEach { | ||
val providerNames = parse(it) | ||
providers.addAll(providerNames.map { getProviderInstance(it, loader, service) }) | ||
} | ||
require(providers.isNotEmpty()) { "No providers were loaded with FastServiceLoader" } | ||
return providers | ||
} | ||
|
||
private fun <S> getProviderInstance(name: String, loader: ClassLoader, service: Class<S>): S { | ||
val clazz = Class.forName(name, false, loader) | ||
require(service.isAssignableFrom(clazz)) { "Expected service of class $service, but found $clazz" } | ||
return service.cast(clazz.getDeclaredConstructor().newInstance()) | ||
} | ||
|
||
private fun parse(url: URL): List<String> { | ||
val string = url.toString() | ||
return if (string.startsWith("jar")) { | ||
val pathToJar = string.substringAfter("jar:file:").substringBefore('!') | ||
val entry = string.substringAfter("!/") | ||
(JarFile(pathToJar, false) as Closeable).use { file -> | ||
BufferedReader(InputStreamReader((file as JarFile).getInputStream(ZipEntry(entry)),"UTF-8")).use { r -> | ||
parseFile(r) | ||
} | ||
} | ||
} else emptyList() | ||
} | ||
|
||
private fun parseFile(r: BufferedReader): List<String> { | ||
val names = mutableSetOf<String>() | ||
while (true) { | ||
val line = r.readLine() ?: break | ||
val serviceName = line.substringBefore("#").trim() | ||
require(serviceName.all { it == '.' || Character.isJavaIdentifierPart(it) }) { "Illegal service provider class name: $serviceName" } | ||
if (serviceName.isNotEmpty()) { | ||
names.add(serviceName) | ||
} | ||
} | ||
return names.toList() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 23 additions & 0 deletions
23
kotlinx-coroutines-core/jvm/test/internal/ServiceLoaderTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package kotlinx.coroutines.internal | ||
|
||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.Delay | ||
import kotlin.test.Test | ||
|
||
class ServiceLoaderTest { | ||
@Test | ||
fun testLoadingSameModuleService() { | ||
val providers = Delay::class.java.let { FastServiceLoader.loadProviders(it, it.classLoader) } | ||
assert(providers.size == 1 && providers[0].javaClass.name == "kotlinx.coroutines.android.DelayImpl") | ||
} | ||
|
||
@Test | ||
fun testCrossModuleService() { | ||
val providers = CoroutineScope::class.java.let { FastServiceLoader.loadProviders(it, it.classLoader) } | ||
assert(providers.size == 3) | ||
val className = "kotlinx.coroutines.android.EmptyCoroutineScopeImpl" | ||
for (i in 1 .. 3) { | ||
assert(providers[i - 1].javaClass.name == "$className$i") | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
...oroutines-android/android-unit-tests/resources/META-INF/services/kotlinx.coroutines.Delay
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
kotlinx.coroutines.android.DelayImpl |
10 changes: 10 additions & 0 deletions
10
ui/kotlinx-coroutines-android/android-unit-tests/src/DelayImpl.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package kotlinx.coroutines.android | ||
|
||
import kotlinx.coroutines.* | ||
|
||
@InternalCoroutinesApi | ||
class DelayImpl : Delay { | ||
override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) { | ||
continuation.cancel() | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
ui/kotlinx-coroutines-android/android-unit-tests/src/EmptyCoroutineScopeImpl.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package kotlinx.coroutines.android | ||
|
||
import kotlinx.coroutines.CoroutineScope | ||
import kotlin.coroutines.CoroutineContext | ||
import kotlin.coroutines.EmptyCoroutineContext | ||
|
||
internal class EmptyCoroutineScopeImpl1 : CoroutineScope { | ||
override val coroutineContext: CoroutineContext | ||
get() = EmptyCoroutineContext | ||
} | ||
|
||
internal class EmptyCoroutineScopeImpl2 : CoroutineScope { | ||
override val coroutineContext: CoroutineContext | ||
get() = EmptyCoroutineContext | ||
} | ||
|
||
internal class EmptyCoroutineScopeImpl3 : CoroutineScope { | ||
override val coroutineContext: CoroutineContext | ||
get() = EmptyCoroutineContext | ||
} |
2 changes: 1 addition & 1 deletion
2
...nes-android/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
kotlinx.coroutines.android.AndroidDispatcherFactory | ||
kotlinx.coroutines.android.AndroidDispatcherFactory |