From 304441b3632db7e88b62e1d022b45c6f04d6b319 Mon Sep 17 00:00:00 2001 From: Osip Fatkullin Date: Tue, 5 Nov 2024 14:54:40 +0100 Subject: [PATCH] KTOR-7698 Make ServiceLoader.load calls optimizable by R8 --- .../jvm/src/io/ktor/client/HttpClientJvm.kt | 11 +++----- .../io/ktor/client/plugins/json/DefaultJvm.kt | 28 +++++++++---------- .../client/tests/utils/ClientLoaderJvm.kt | 11 ++++---- .../io/ktor/server/config/ConfigLoadersJvm.kt | 10 +++---- .../ktor/serialization/kotlinx/Extensions.kt | 4 +-- .../serialization/kotlinx/ExtensionsJs.kt | 4 +-- .../serialization/kotlinx/ExtensionsJvm.kt | 12 ++++---- .../serialization/kotlinx/ExtensionsNative.kt | 4 +-- .../src/io/ktor/util/reflect/ServiceLoader.kt | 25 +++++++++++++++++ 9 files changed, 64 insertions(+), 45 deletions(-) create mode 100644 ktor-utils/jvm/src/io/ktor/util/reflect/ServiceLoader.kt diff --git a/ktor-client/ktor-client-core/jvm/src/io/ktor/client/HttpClientJvm.kt b/ktor-client/ktor-client-core/jvm/src/io/ktor/client/HttpClientJvm.kt index 4becddf94b5..446cc2b62ab 100644 --- a/ktor-client/ktor-client-core/jvm/src/io/ktor/client/HttpClientJvm.kt +++ b/ktor-client/ktor-client-core/jvm/src/io/ktor/client/HttpClientJvm.kt @@ -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.* @@ -34,12 +35,8 @@ public interface HttpClientEngineContainer { public val factory: HttpClientEngineFactory<*> } -/** - * Workaround for dummy android [ClassLoader]. - */ -private val engines: List = HttpClientEngineContainer::class.java.let { - ServiceLoader.load(it, it.classLoader).toList() -} +@OptIn(InternalAPI::class) +private val engines = loadService() private val FACTORY = engines.firstOrNull()?.factory ?: error( "Failed to find HTTP client engine implementation in the classpath: consider adding client engine dependency. " + diff --git a/ktor-client/ktor-client-plugins/ktor-client-json/jvm/src/io/ktor/client/plugins/json/DefaultJvm.kt b/ktor-client/ktor-client-plugins/ktor-client-json/jvm/src/io/ktor/client/plugins/json/DefaultJvm.kt index f9e1f1d200b..109b1350ee8 100644 --- a/ktor-client/ktor-client-plugins/ktor-client-json/jvm/src/io/ktor/client/plugins/json/DefaultJvm.kt +++ b/ktor-client/ktor-client-plugins/ktor-client-json/jvm/src/io/ktor/client/plugins/json/DefaultJvm.kt @@ -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() + 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() + ) } diff --git a/ktor-client/ktor-client-tests/jvm/src/io/ktor/client/tests/utils/ClientLoaderJvm.kt b/ktor-client/ktor-client-tests/jvm/src/io/ktor/client/tests/utils/ClientLoaderJvm.kt index 8bbb1ed9fe0..0290e60a1b7 100644 --- a/ktor-client/ktor-client-tests/jvm/src/io/ktor/client/tests/utils/ClientLoaderJvm.kt +++ b/ktor-client/ktor-client-tests/jvm/src/io/ktor/client/tests/utils/ClientLoaderJvm.kt @@ -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.* @@ -16,11 +18,8 @@ import kotlin.time.Duration.Companion.seconds */ actual abstract class ClientLoader actual constructor(val timeoutSeconds: Int) { - private val engines: List by lazy { - HttpClientEngineContainer::class.java.let { engineContainerClass -> - ServiceLoader.load(engineContainerClass, engineContainerClass.classLoader).toList() - } - } + @OptIn(InternalAPI::class) + private val engines: Iterable by lazy { loadService() } /** * Perform test against all clients from dependencies. diff --git a/ktor-server/ktor-server-core/jvm/src/io/ktor/server/config/ConfigLoadersJvm.kt b/ktor-server/ktor-server-core/jvm/src/io/ktor/server/config/ConfigLoadersJvm.kt index 36a172e4744..fe0e99a478b 100644 --- a/ktor-server/ktor-server-core/jvm/src/io/ktor/server/config/ConfigLoadersJvm.kt +++ b/ktor-server/ktor-server-core/jvm/src/io/ktor/server/config/ConfigLoadersJvm.kt @@ -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 get() = listOfNotNull( @@ -14,6 +15,5 @@ internal actual val CONFIG_PATH: List getEnvironmentProperty("config.url"), ) -public actual val configLoaders: List = ConfigLoader::class.java.let { - ServiceLoader.load(it, it.classLoader).toList() -} +@OptIn(InternalAPI::class) +public actual val configLoaders: List = loadService().toList() diff --git a/ktor-shared/ktor-serialization/ktor-serialization-kotlinx/common/src/io/ktor/serialization/kotlinx/Extensions.kt b/ktor-shared/ktor-serialization/ktor-serialization-kotlinx/common/src/io/ktor/serialization/kotlinx/Extensions.kt index 4853e1472d4..61917608333 100644 --- a/ktor-shared/ktor-serialization/ktor-serialization-kotlinx/common/src/io/ktor/serialization/kotlinx/Extensions.kt +++ b/ktor-shared/ktor-serialization/ktor-serialization-kotlinx/common/src/io/ktor/serialization/kotlinx/Extensions.kt @@ -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 @@ -11,7 +11,7 @@ import io.ktor.utils.io.* import io.ktor.utils.io.charsets.* import kotlinx.serialization.* -internal expect val providers: List +internal expect val providers: Iterable internal fun extensions(format: SerialFormat): List = providers.mapNotNull { it.extension(format) } diff --git a/ktor-shared/ktor-serialization/ktor-serialization-kotlinx/jsAndWasmShared/src/io/ktor/serialization/kotlinx/ExtensionsJs.kt b/ktor-shared/ktor-serialization/ktor-serialization-kotlinx/jsAndWasmShared/src/io/ktor/serialization/kotlinx/ExtensionsJs.kt index 835b080ece9..dff81cd0831 100644 --- a/ktor-shared/ktor-serialization/ktor-serialization-kotlinx/jsAndWasmShared/src/io/ktor/serialization/kotlinx/ExtensionsJs.kt +++ b/ktor-shared/ktor-serialization/ktor-serialization-kotlinx/jsAndWasmShared/src/io/ktor/serialization/kotlinx/ExtensionsJs.kt @@ -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 +internal actual val providers: Iterable get() = emptyList() diff --git a/ktor-shared/ktor-serialization/ktor-serialization-kotlinx/jvm/src/io/ktor/serialization/kotlinx/ExtensionsJvm.kt b/ktor-shared/ktor-serialization/ktor-serialization-kotlinx/jvm/src/io/ktor/serialization/kotlinx/ExtensionsJvm.kt index fc1515bc70c..06e585fa67f 100644 --- a/ktor-shared/ktor-serialization/ktor-serialization-kotlinx/jvm/src/io/ktor/serialization/kotlinx/ExtensionsJvm.kt +++ b/ktor-shared/ktor-serialization/ktor-serialization-kotlinx/jvm/src/io/ktor/serialization/kotlinx/ExtensionsJvm.kt @@ -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::class.java.let { - ServiceLoader.load(it, it.classLoader).toList() - } +@OptIn(InternalAPI::class) +internal actual val providers: Iterable = + loadService() diff --git a/ktor-shared/ktor-serialization/ktor-serialization-kotlinx/posix/src/io/ktor/serialization/kotlinx/ExtensionsNative.kt b/ktor-shared/ktor-serialization/ktor-serialization-kotlinx/posix/src/io/ktor/serialization/kotlinx/ExtensionsNative.kt index 36c00f6d6aa..8ae6d44eed7 100644 --- a/ktor-shared/ktor-serialization/ktor-serialization-kotlinx/posix/src/io/ktor/serialization/kotlinx/ExtensionsNative.kt +++ b/ktor-shared/ktor-serialization/ktor-serialization-kotlinx/posix/src/io/ktor/serialization/kotlinx/ExtensionsNative.kt @@ -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 @@ -7,7 +7,7 @@ package io.ktor.serialization.kotlinx import io.ktor.utils.io.* private val _providers: MutableList = mutableListOf() -internal actual val providers: List = _providers +internal actual val providers: Iterable = _providers @InternalAPI public fun addExtensionProvider(provider: KotlinxSerializationExtensionProvider) { diff --git a/ktor-utils/jvm/src/io/ktor/util/reflect/ServiceLoader.kt b/ktor-utils/jvm/src/io/ktor/util/reflect/ServiceLoader.kt new file mode 100644 index 00000000000..98be40822cf --- /dev/null +++ b/ktor-utils/jvm/src/io/ktor/util/reflect/ServiceLoader.kt @@ -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 loadService(): Iterable { + val iterator = ServiceLoader.load( + T::class.java, + T::class.java.classLoader, + ).iterator() + + return Iterable { iterator } +}