kord
kord copied to clipboard
Status on GraalVM native image compatibility
Support GraalVM native images
In addition to supporting Kotlin/JS and Kotlin/Native (See #69) we should provide Support for GraalVM native image.
Tasks
- [x] Generate GraalVM runtime hints for Serializable classes and cache data classes
- [x] Provide example
- [ ] Write readme for example
- [x] Kotlin Support for GraalVM
- [ ] KTor support for GraalVM
- [ ] MocKK support for GraalVM
Experimental builds
Currently, we provide an experimental Graal build (See #787), this build is not fully tested and not recommended for production use
You can obtain it using the feature-graal-SNAPSHOT
version. See the Project README for more information
Original Issue
I saw that you're working on kotlin native/multiplatform support and was wondering if GraalVM Native Image is also something that you're interested in supporting in the future.
Some context: I'm currently trying to build a native image for one of my side projects that uses kord and there are a lot of runtime hints required to get everything working. I was hoping that kord could provide such runtime hints so that i don't have to maintain it if something changes under the hood.
Never used Graal before but as the main guy of working on MPP here I am definitely interested in it. However I don't know what the problem here is as I haven't tried it yet.
What happened when you try to build a Graal image?
Another thing is about dependency compatibility. I only know that ktor server supports Graal not sure about the client
Never used Graal before but as the main guy of working on MPP here I am definitely interested in it.
Great to hear that you're interested ^^
What happened when you try to build a Graal image?
Some things wont work out of the box when an application is compiled using GraalVM Native Image. All limitations are described here: https://www.graalvm.org/22.1/reference-manual/native-image/Limitations/
As kord relies on kotlinx-serialization, i currently need to provide reflection hints for all @Serializable
annotated classes. One example would be:
reflect-config.json
:
[
{
"name": "dev.kord.common.entity.ApplicationCommandPermissionType",
"allDeclaredFields": true,
"allDeclaredConstructors": true,
"queryAllDeclaredMethods": true,
"methods": [
{
"name": "getValue",
"parameterTypes": [ ]
}
]
},
{
"name": "dev.kord.common.entity.ApplicationCommandPermissionType$Companion",
"methods": [
{
"name": "serializer",
"parameterTypes": [ ]
}
]
}
]
The above configuration would tell GraalVM that it should expect reflection calls for fields, methods and constructors of ApplicationCommandPermissionType
and also for the generated serializer
method of its companion object. All of these calls are required during serialization. Without those hints, you'd see NoSuchFieldException
s (or similar depending on the reflection call).
The same also applies to ktor-client to some extend. However, I'd expect the ktor team to provide compatibility for GraalVM in this case as there are no kord specific classes involved. Only for completeness, the following hints were required in my project:
// required by kotlin coroutines
.registerType(TypeReference.of("kotlin.internal.jdk8.JDK8PlatformImplementations"), MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)
// required by ktor
.registerType(InterestSuspensionsMap::class.java, MemberCategory.DECLARED_FIELDS)
.registerType(DefaultPool::class.java, MemberCategory.DECLARED_FIELDS)
(The above code is using Spring Frameworks RuntimeHintsRegistrar to generate json format that GraalVM expects during compilation.)
Please note, that i'm still very early in the testing phase and that there are a lot of hints still missing (not only for kord but also for other libraries). I'm currently still trying to get the application to boot, which also involves connecting to the discord gateway. That's the main reason why i've started with runtime hints for kord :D
And again, just for completeness, this is the branch i'm currently working on: https://github.com/DarkAtra/v-rising-discord-bot/tree/next
// required by kotlin coroutines .registerType(TypeReference.of("kotlin.internal.jdk8.JDK8PlatformImplementations"), MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)
This should be fixed in Kotlin 1.9 and is tracked in KT-51579
I've created a test project and I can run using the following config
reflect-config.json
[
{
"name": "kotlin.internal.jdk8.JDK8PlatformImplementations",
"allPublicMethods": true,
"allDeclaredFields": true,
"allDeclaredMethods": true,
"allDeclaredConstructors": true
},
{
"name": "io.ktor.network.selector.InterestSuspensionsMap",
"allDeclaredFields": true
},
{
"name": "io.ktor.utils.io.pool.DefaultPool",
"allDeclaredFields": true
}
]
Main.kt
package dev.kord.core
import dev.kord.core.event.message.MessageCreateEvent
import dev.kord.gateway.Intent
import dev.kord.gateway.PrivilegedIntent
suspend fun main(args: Array<String>) {
val kord = Kord(args.firstOrNull() ?: error("token required"))
kord.on<MessageCreateEvent> {
if (message.author?.isBot == true) return@on
if (message.content == "!ping") message.channel.createMessage("pong")
}
kord.login {
presence { playing("!ping to pong") }
@OptIn(PrivilegedIntent::class)
intents += Intent.MessageContent
}
}
build.gradle.kts
plugins {
`kord-internal-module`
application
id("org.graalvm.buildtools.native") version "0.9.20"
}
dependencies {
implementation(projects.core)
implementation(libs.slf4j.simple)
}
application {
mainClass.set("dev.kord.core.MainKt")
}
graalvmNative {
binaries {
named("main") {
javaLauncher.set(javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(19))
vendor.set(JvmVendorSpec.matching("GraalVM Community"))
})
}
}
}
Could you share an example of what causes issues with serialization?
Also, would you mind joining the Support Discord, so we can discuss this further?
After further investigation, the only method using reflection is serializer<Type>(), which should be easy to generate
{
"name":"package.name.YourClassName",
"fields":[{"name":"Companion"}]
},
{
"name":"package.name.YourClassName$Companion",
"methods":[{"name":"serializer","parameterTypes":[] }]
}
Could you please try the version feature-graal-SNAPSHOT
and tell me what issues you encounter?
I've created a test project and I can run using the following config reflect-config.json Main.kt build.gradle.kts
Could you share an example of what causes issues with serialization?
Also, would you mind joining the Support Discord, so we can discuss this further?
After further investigation, the only method using reflection is serializer(), which should be easy to generate
{ "name":"package.name.YourClassName", "fields":[{"name":"Companion"}] }, { "name":"package.name.YourClassName$Companion", "methods":[{"name":"serializer","parameterTypes":[] }] }
joined discord in case you still want to discuss something.
Could you please try the version
feature-graal-SNAPSHOT
and tell me what issues you encounter?
i'll try the snapshot now
with the snapshot it does no longer require runtime hints for:
ApplicationCommandData::class.java,
AutoModerationRuleData::class.java,
ChannelData::class.java,
EmojiData::class.java,
GuildData::class.java,
MemberData::class.java,
MessageData::class.java,
PresenceData::class.java,
RoleData::class.java,
StickerData::class.java,
ThreadMemberData::class.java,
UserData::class.java,
VoiceStateData::class.java,
WebhookData::class.java
but the following hints are still required (probably because i am registering GlobalApplicationCommands
on startup):
{
"name": "dev.kord.core.cache.data.GuildApplicationCommandPermissionsData"
},
{
"name": "dev.kord.core.cache.data.StickerPackData"
},
without i'm getting the followng exception:
kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Unresolved class: class dev.kord.core.cache.data.GuildApplicationCommandPermissionsData
at kotlin.reflect.jvm.internal.KClassImpl.reportUnresolvedClass(KClassImpl.kt:328) ~[v-rising-discord-bot:1.8.10-release-430(1.8.10)]
at kotlin.reflect.jvm.internal.KClassImpl.access$reportUnresolvedClass(KClassImpl.kt:44) ~[v-rising-discord-bot:1.8.10-release-430(1.8.10)]
at kotlin.reflect.jvm.internal.KClassImpl$Data$descriptor$2.invoke(KClassImpl.kt:56) ~[na:na]
at kotlin.reflect.jvm.internal.KClassImpl$Data$descriptor$2.invoke(KClassImpl.kt:48) ~[na:na]
at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:93) ~[na:na]
at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32) ~[v-rising-discord-bot:1.8.10-release-430(1.8.10)]
at kotlin.reflect.jvm.internal.KClassImpl$Data.getDescriptor(KClassImpl.kt:48) ~[v-rising-discord-bot:1.8.10-release-430(1.8.10)]
at kotlin.reflect.jvm.internal.KClassImpl.getDescriptor(KClassImpl.kt:182) ~[v-rising-discord-bot:1.8.10-release-430(1.8.10)]
at kotlin.reflect.jvm.internal.KClassImpl.getDescriptor(KClassImpl.kt:44) ~[v-rising-discord-bot:1.8.10-release-430(1.8.10)]
at kotlin.reflect.full.KClassifiers.createType(KClassifiers.kt:47) ~[na:na]
at kotlin.reflect.jvm.internal.CachesKt$CACHE_FOR_BASE_CLASSIFIERS$1.invoke(caches.kt:37) ~[na:na]
at kotlin.reflect.jvm.internal.CachesKt$CACHE_FOR_BASE_CLASSIFIERS$1.invoke(caches.kt:36) ~[na:na]
at kotlin.reflect.jvm.internal.ComputableClassValue.computeValue(CacheByClass.kt:48) ~[na:na]
at kotlin.reflect.jvm.internal.ComputableClassValue.computeValue(CacheByClass.kt:46) ~[na:na]
at [email protected]/java.lang.ClassValue.get(JavaLangSubstitutions.java:590) ~[v-rising-discord-bot:na]
at kotlin.reflect.jvm.internal.ClassValueCache.get(CacheByClass.kt:61) ~[na:na]
at kotlin.reflect.jvm.internal.CachesKt.getOrCreateKType(caches.kt:55) ~[na:na]
at kotlin.reflect.jvm.internal.ReflectionFactoryImpl.typeOf(ReflectionFactoryImpl.java:123) ~[v-rising-discord-bot:1.8.10-release-430(1.8.10)]
at kotlin.jvm.internal.Reflection.typeOf(Reflection.java:128) ~[na:na]
at dev.kord.core.cache.data.GuildApplicationCommandPermissionsData.<clinit>(GuildApplicationCommandPermissionsData.kt:35) ~[na:na]
at dev.kord.core.cache.DataCacheExtensionsKt.registerKordData(DataCacheExtensions.kt:23) ~[na:na]
at dev.kord.core.builder.kord.KordBuilder.build(KordBuilder.kt:276) ~[na:na]
at dev.kord.core.builder.kord.KordBuilder$build$1.invokeSuspend(KordBuilder.kt) ~[na:na]
Runtime hints for ktor and kotlin coroutines are also still required but that is totally fine as it's not something i'd expect kord to provide.
We will revisit this after #855
Since #928 got merged, the Ktor reflection hints might no longer be needed, as those seem to be CIO specific
Since #928 got merged, the Ktor reflection hints might no longer be needed, as those seem to be CIO specific
Seems like the hint for DefaultPool
is still required. The one for InterestSuspensionsMap
can be omitted though. E.g.:
reflect-config.json
(see https://github.com/DarkAtra/v-rising-discord-bot/commit/fd1d786daf1e2a92dc1781e173b376cd7ac924e7):
[
{
"name": "kotlin.internal.jdk8.JDK8PlatformImplementations",
"allPublicMethods": true,
"allDeclaredFields": true,
"allDeclaredMethods": true,
"allDeclaredConstructors": true
},
{
"name": "io.ktor.utils.io.pool.DefaultPool",
"allDeclaredFields": true
}
]
kotlin.internal.jdk8.JDK8PlatformImplementations
should no longer be needed as of 1.9.0
In the project i linked i'm stuck with Kotlin 1.8.x for the time being due to https://github.com/oracle/graal/issues/7089. However, i validated that a different project (which already uses kotlin 1.9.23) runs fine without JDK8PlatformImplementations
hints.
Small update... It seems like Kord also needs to provide reflection hints for the following two classes when using newer GraalVM versions:
-
Optional.Null.Companion::class
-
Optional.Missing.Companion::class
Additionally, kotlinx.serialization also requires a few reflection hints for (in newer GraalVM versions):
-
JsonArray.Companion::class
-
JsonObject.Companion::class
(see https://github.com/oracle/graal/issues/7089)