badass-jlink-plugin
badass-jlink-plugin copied to clipboard
Kotlin Coroutines `Cannot derive uses clause from service loader invocation ...` (Ktor)
Hello,
I'd like to preface that I am new to the module system introduced in Java 9, so bare with me if this is a silly question.
Context: I am building a GUI using JavaFX that interacts with an API through HTTP requests. I am using Ktor for the HTTP client. Goal: I want to use the JLink plugin to create an image of my app that others can run. Problem: When Ktor launches a coroutine, the application crashes with the following stack trace:
Exception in thread "DefaultDispatcher-worker-1" java.lang.NoClassDefFoundError: Could not initialize class kotlinx.coroutines.CoroutineExceptionHandlerImplKt
at [email protected]/kotlinx.coroutines.CoroutineExceptionHandlerKt.handleCoroutineException(Unknown Source)
at [email protected]/kotlinx.coroutines.internal.LimitedDispatcher.run(Unknown Source)
at [email protected]/kotlinx.coroutines.scheduling.TaskImpl.run(Unknown Source)
at [email protected]/kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(Unknown Source)
at [email protected]/kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(Unknown Source)
at [email protected]/kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(Unknown Source)
at [email protected]/kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(Unknown Source)
My initial configuration looked as follows:
plugins {
java
application
id("org.openjfx.javafxplugin") version "0.0.13"
id("org.beryx.jlink") version "2.25.0"
kotlin("jvm") version "1.6.21"
kotlin("plugin.serialization") version "1.6.21"
}
group = "nl.rug"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
val ktor_version = "2.1.1"
val coroutine_version = "1.6.4"
implementation("org.kordamp.ikonli:ikonli-javafx:12.3.1")
implementation("io.ktor:ktor-client-core:${ktor_version}")
implementation("io.ktor:ktor-client-cio:${ktor_version}")
implementation("io.ktor:ktor-client-content-negotiation:${ktor_version}")
implementation("io.ktor:ktor-serialization-kotlinx-json:${ktor_version}")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-slf4j:$coroutine_version")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:$coroutine_version")
implementation("no.tornado:tornadofx:1.7.20")
}
application {
mainModule.set("nl.rug.gscholarfx")
mainClass.set("nl.rug.gscholarfx.ScholarApplication")
applicationName = "Google Scholar GUI"
applicationDefaultJvmArgs = listOf("--add-reads", "kotlin.stdlib=kotlinx.coroutines.core.jvm")
}
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(17))
}
javafx {
version = "17.0.2"
modules(
"javafx.controls",
"javafx.fxml",
)
}
jlink {
options.set(listOf("--strip-debug", "--compress", "2", "--no-header-files", "--no-man-pages"))
addExtraDependencies("javafx", "kotlinx-datetime", "kotlinx-coroutines", "kotlinx-serialization")
launcher {
name = "Google Scholar GUI"
jvmArgs = listOf("--add-reads","kotlin.stdlib=kotlinx.coroutines.core.jvm")
noConsole = false
}
}
tasks {
withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions.jvmTarget = "17"
}
}
Whenever I run the :createMergedModule Gradle task, I get the following warnings
> Task :createMergedModule
Cannot derive uses clause from service loader invocation in: kotlinx/coroutines/internal/MainDispatcherLoader.loadMainDispatcher().
Cannot derive uses clause from service loader invocation in: kotlinx/coroutines/internal/FastServiceLoader.load().
Cannot derive uses clause from service loader invocation in: kotlinx/coroutines/CoroutineExceptionHandlerImplKt.<clinit>().
Cannot derive uses clause from service loader invocation in: io/ktor/client/HttpClientJvmKt.<clinit>().
Cannot derive uses clause from service loader invocation in: kotlin/reflect/jvm/internal/impl/resolve/OverridingUtil.<clinit>().
Cannot derive uses clause from service loader invocation in: kotlin/reflect/jvm/internal/impl/builtins/BuiltInsLoader$Companion$Instance$2.invoke().
Cannot derive uses clause from service loader invocation in: kotlin/reflect/jvm/internal/impl/descriptors/Visibilities.<clinit>().
This lead me to believe I have to manually provide merged module info, so I ran the :suggestMergedModuleInfo task and this gave me the following suggestions:
mergedModule {
requires("javafx.graphics")
requires("java.management")
requires("javafx.controls")
requires("java.json")
requires("java.logging")
requires("java.prefs")
requires("java.desktop")
requires("javafx.base")
requires("java.instrument")
requires("javafx.fxml")
requires("jdk.unsupported")
uses("tornadofx.Stylesheet")
uses("tornadofx.ChildInterceptor")
provides("io.ktor.client.HttpClientEngineContainer").with("io.ktor.client.engine.cio.CIOEngineContainer")
provides("kotlin.reflect.jvm.internal.impl.resolve.ExternalOverridabilityCondition")
.with("kotlin.reflect.jvm.internal.impl.load.java.ErasedOverridabilityCondition",
"kotlin.reflect.jvm.internal.impl.load.java.FieldOverridabilityCondition",
"kotlin.reflect.jvm.internal.impl.load.java.JavaIncompatibilityRulesOverridabilityCondition")
provides("kotlin.reflect.jvm.internal.impl.builtins.BuiltInsLoader")
.with("kotlin.reflect.jvm.internal.impl.serialization.deserialization.builtins.BuiltInsLoaderImpl")
provides("kotlinx.coroutines.internal.MainDispatcherFactory")
.with("kotlinx.coroutines.javafx.JavaFxDispatcherFactory")
}
When I include this in the jlink { ... } configuration block, the warning messages shown in the :createMergedModule Gradle task are gone, however when I run the launcher in the generated image, I still get the same exception as before. It does run perfectly fine using the :run task from the application Gradle plugin.
Could anyone shed some light on why this happens, and how I should go about fixing it?
Thanks!
I should note that running coroutines using my own defined CoroutineScope works perfectly fine, e.g.
val searchScope = CoroutineScope(Dispatchers.IO)
authorSearchButton.apply {
disableWhen(authorSearchField.textProperty().isEmpty)
setOnAction {
searchScope.launch {
println("this is reached")
val searchProfileResponse = ScholarClient.fetchProfiles(authorSearchField.text)
println("this is not")
withContext(Dispatchers.JavaFx) {
authorListView.items.setAll(searchProfileResponse.profiles)
}
}
}
}
Here is the implementation of ScholarClient.fetchProfiles:
// Crashes when it calls this method
suspend fun fetchProfiles(mauthors: String): SearchProfileResponse =
client.get(Endpoint.Profiles.makeUrl() + "&mauthors=$mauthors").body()
Similar issue: https://github.com/Kotlin/kotlinx.coroutines/issues/3032
The latest Kotlin version has Java module support.