Skip to content

Commit

Permalink
KTOR-7698 Make ServiceLoader.load calls optimizable by R8
Browse files Browse the repository at this point in the history
  • Loading branch information
osipxd committed Nov 6, 2024
1 parent 1261aa4 commit 304441b
Show file tree
Hide file tree
Showing 9 changed files with 64 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/*
* Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.client

import io.ktor.client.engine.*
import io.ktor.util.reflect.*
import io.ktor.utils.io.*
import java.util.*

Expand Down Expand Up @@ -34,12 +35,8 @@ public interface HttpClientEngineContainer {
public val factory: HttpClientEngineFactory<*>
}

/**
* Workaround for dummy android [ClassLoader].
*/
private val engines: List<HttpClientEngineContainer> = HttpClientEngineContainer::class.java.let {
ServiceLoader.load(it, it.classLoader).toList()
}
@OptIn(InternalAPI::class)
private val engines = loadService<HttpClientEngineContainer>()

private val FACTORY = engines.firstOrNull()?.factory ?: error(
"Failed to find HTTP client engine implementation in the classpath: consider adding client engine dependency. " +
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
/*
* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.client.plugins.json

import java.util.*
import io.ktor.util.reflect.*
import io.ktor.utils.io.*

@OptIn(InternalAPI::class)
@Suppress("DEPRECATION_ERROR")
public actual fun defaultSerializer(): JsonSerializer {
val serializers = ServiceLoader.load(JsonSerializer::class.java)
.toList()

if (serializers.isEmpty()) {
error(
"""Fail to find serializer. Consider to add one of the following dependencies:
- ktor-client-gson
- ktor-client-json
- ktor-client-serialization"""
)
}

return serializers.maxByOrNull { it::javaClass.name }!!
val serializers = loadService<JsonSerializer>()
return serializers.maxByOrNull { it::javaClass.name } ?: error(
"""
Failed to find serializer. Consider adding one of the following dependencies:
- ktor-client-gson
- ktor-client-json
- ktor-client-serialization
""".trimIndent()
)
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
/*
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.client.tests.utils

import io.ktor.client.*
import io.ktor.client.engine.*
import io.ktor.util.reflect.*
import io.ktor.utils.io.*
import kotlinx.coroutines.*
import kotlinx.coroutines.debug.*
import java.util.*
Expand All @@ -16,11 +18,8 @@ import kotlin.time.Duration.Companion.seconds
*/
actual abstract class ClientLoader actual constructor(val timeoutSeconds: Int) {

private val engines: List<HttpClientEngineContainer> by lazy {
HttpClientEngineContainer::class.java.let { engineContainerClass ->
ServiceLoader.load(engineContainerClass, engineContainerClass.classLoader).toList()
}
}
@OptIn(InternalAPI::class)
private val engines: Iterable<HttpClientEngineContainer> by lazy { loadService<HttpClientEngineContainer>() }

/**
* Perform test against all clients from dependencies.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/*
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.server.config

import io.ktor.server.engine.*
import java.util.*
import io.ktor.util.reflect.*
import io.ktor.utils.io.*

internal actual val CONFIG_PATH: List<String>
get() = listOfNotNull(
Expand All @@ -14,6 +15,5 @@ internal actual val CONFIG_PATH: List<String>
getEnvironmentProperty("config.url"),
)

public actual val configLoaders: List<ConfigLoader> = ConfigLoader::class.java.let {
ServiceLoader.load(it, it.classLoader).toList()
}
@OptIn(InternalAPI::class)
public actual val configLoaders: List<ConfigLoader> = loadService<ConfigLoader>().toList()
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.serialization.kotlinx
Expand All @@ -11,7 +11,7 @@ import io.ktor.utils.io.*
import io.ktor.utils.io.charsets.*
import kotlinx.serialization.*

internal expect val providers: List<KotlinxSerializationExtensionProvider>
internal expect val providers: Iterable<KotlinxSerializationExtensionProvider>

internal fun extensions(format: SerialFormat): List<KotlinxSerializationExtension> =
providers.mapNotNull { it.extension(format) }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/*
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.serialization.kotlinx

internal actual val providers: List<KotlinxSerializationExtensionProvider>
internal actual val providers: Iterable<KotlinxSerializationExtensionProvider>
get() = emptyList()
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/*
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.serialization.kotlinx

import java.util.*
import io.ktor.util.reflect.*
import io.ktor.utils.io.*

internal actual val providers: List<KotlinxSerializationExtensionProvider> =
KotlinxSerializationExtensionProvider::class.java.let {
ServiceLoader.load(it, it.classLoader).toList()
}
@OptIn(InternalAPI::class)
internal actual val providers: Iterable<KotlinxSerializationExtensionProvider> =
loadService<KotlinxSerializationExtensionProvider>()
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/*
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.serialization.kotlinx

import io.ktor.utils.io.*

private val _providers: MutableList<KotlinxSerializationExtensionProvider> = mutableListOf()
internal actual val providers: List<KotlinxSerializationExtensionProvider> = _providers
internal actual val providers: Iterable<KotlinxSerializationExtensionProvider> = _providers

@InternalAPI
public fun addExtensionProvider(provider: KotlinxSerializationExtensionProvider) {
Expand Down
25 changes: 25 additions & 0 deletions ktor-utils/jvm/src/io/ktor/util/reflect/ServiceLoader.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.util.reflect

import io.ktor.utils.io.*
import java.util.*

/**
* Loads implementations of service [T] using [ServiceLoader.load].
*
* NOTE: ServiceLoader should use specific call convention to be optimized by R8 on Android:
* `ServiceLoader.load(X.class, X.class.getClassLoader()).iterator()`
* See: https://r8.googlesource.com/r8/+/refs/heads/main/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
*/
@InternalAPI
public inline fun <reified T> loadService(): Iterable<T> {
val iterator = ServiceLoader.load(
T::class.java,
T::class.java.classLoader,
).iterator()

return Iterable { iterator }
}

0 comments on commit 304441b

Please sign in to comment.