Closed akoufa closed 3 years ago
cc @evanchooly
We're working on a formal timeline but unofficially here's my short term plan:
As for coroutines in native mode, it's hard to tag that with a specific timeline because the interactions with graalvm make that one hard to gauge. It's likely that one will run in parallel with other tasks until we can crack that code. But I haven't yet dug in to it so it's all speculative at this point.
The hope is to have some meetings this week. There's an issue to track the official roadmap if you want to follow along: https://github.com/quarkusio/quarkus/issues/8194
Also, my unordered, unprioritized working list: https://github.com/quarkusio/quarkus/labels/area%2Fkotlin
@cescoffier can you CC Arthur so he can put some progress as he finds stuff on the native image compilation for Kotlin co-routines
Does it make any conceptual sense that Vert.x has a Kotlin API generator, and whether we should extend this for Quarkus @cescoffier
@Tas-Hur <-- That should be Arthur
Hi, I checked out the fix that allows the compiler to solve issues with irreducible loops : https://github.com/oracle/graal/commit/4662877b8ce214528f09553f21776ab97e97c1a8 It is actually quite simple, to avoid multiple entry points in a loop only one entry point is kept and the other ones are replaced with a copy of the code contained inside the loop. However the compiler has a policy about the number of duplications it allows.
I am currently running some tests to have a better idea of how nesting loops and calling functions with several points of suspension influences the number of duplication. For now I have this basic scenario showing a linear relation between the maximum number of suspending calls and the value of MaxDuplicationFactor :
MaxDuplicationFactor = 1 compiles up to 5 suspending calls in a loop
MaxDuplicationFactor = 2 compiles up to 10 suspending calls in a loop
MaxDuplicationFactor = 3 compiles up to 15 suspending calls in a loop
...
MaxDuplicationFactor = 10 compiles up to 50 suspending calls in a loop
webflux works with suspend fun now. maybe you can refer to it?
Hello to all. Any updates on supporting Kotlin Coroutines ?
The absence of support for Kotlin coroutines is really the only thing keeping my team from using Quarkus right now. Guess we gotta stick with Spring+WebFlux for a while...
I believe Kotlin co-routines are supported in JVM mode. I don't see why they should not. Also, with the recent GraalVM changes, it may work in native (but I would be a bit more careful about that).
Ping @evanchooly
Yeah, coroutines should absolutely work in JVM mode. There were some significant hurdles the last time someone looked at native mode but I haven't had a chance just yet. It's on the list to revisit soon, though. Hopefully JetBrains and the graalvm team have worked out some of the issues.
now dev mode might be another issue but I think that's literally the next item on my list to tackle.
@evanchooly @cescoffier Kotlin Coroutines can be used in Quarkus JVM mode but If I am not mistaken there are some heavy limitations. For example:
It is not possible to define suspend
Jax-RS Routes. Currently a workaround has to be used to convert from a coroutine to Jax-RS Mutiny. Also Quarkus does not provide any executors needed to run Kotlin Coroutines. Again we have to use the ones from Infrastructure.getDefaultExecutor()
of Mutiny or some other workaround. What about context propagation ?
Quarkus performance could be improved if threading could be controlled by the application code using Kotlin Coroutines fine grained mechanisms. Starting from a suspend
Jax-RS endpoint which runs on the IO Pool and then if needed with the Kotlin Coroutines construct withContext to change to the Worker Pool, for example when calling a blocking API like Hibernate or not when using Hibernate Reactive or some other non blocking API.
I think it would a huge improvement in Quarkus Kotlin experience if it would be possible to address these issues considering that these features are already available in Spring Boot or Micronaut.
Nevertheless I have to thank you for your work so far in supporting Kotlin in Quarkus.
Yeah, coroutines should absolutely work in JVM mode. There were some significant hurdles the last time someone looked at native mode but I haven't had a chance just yet. It's on the list to revisit soon, though. Hopefully JetBrains and the graalvm team have worked out some of the issues.
Came across this issue. Be advised of https://medium.com/graalvm/graalvm-20-1-7ce7e89f066b. Works great in my testing so far. Had significant issues pre graal 20.1
@evanchooly @cescoffier Now that Mutiny has a nice integration with Kotlin Coroutines and we also got RestEasy Reactive is there a possibility to support RestEasy routes as suspending functions e.g. suspend fun
?
I really like the reactive routes and just launch
with the vert.x couroutine dispatcher in there.
Works out really well also with child-coroutines using the Dispatchers.IO context - even in native mode with a current GraalVM (20.3).
For most cases, it's even simpler with mutiny-kotlin to just do a async { ... }.asUni()
since reactive routes accept Unis and Multis as return type - no manual routingContext handling anymore.
What do you (all) think about a quarkus-vertx-web-kotlin
extension utilizing reactive routes to provide something like that (example from the reactive routes guide):
@Route(path = "/greetings", methods = [HttpMethod.GET])
suspend fun greetings(ex: RoutingExchange) {
ex.ok("hello " + ex.getParam("name").orElse("world"))
}
These methods would be launched on the vert.x coroutine dispatcher and would necessarily interact completely via the RoutingExchange.
Or a solution some kind of similar to the routing DSL of Jetbrains Ktor what is really handy to work with - than the routes could directly be registered on vert.x routes.
@heubeck Actually it would be even better if suspendable functions would supported in RestEasy Reactive which is like Reactive Routes but more high level and more declarative.
@GET
@Path("/{name}")
suspend fun greetings(@RestPath name: String): String {
return "$name"
}
As it is build also on top of Quarkus Vertx Layer that should be feasible.
@evanchooly is looking into adding proper Corroutine support into RESTEasy Reactive, so watch this space for updates :)
@akoufa Until RESTEasy Reactive includes coroutines support. I used your guidance to create the following "scope" function to adapt resource methods.
fun <T, R> T.resourceScope(block: suspend T.() -> R): Uni<R> =
GlobalScope.async(Infrastructure.getDefaultExecutor().asCoroutineDispatcher()) {
block.invoke(this@resourceScope)
}.asUni()
It's used as...
class TestCoroutineResource {
fun test(): Uni<String> = resourceScope {
...
}
}
Is this considered to be efficient or is it dispatching to worker threads and waiting on them? Other than knowing the executor is the Mutiny default executor I have no idea if that's the executor Quarkus uses internally.
Barring unforeseen events, Kotlin Coroutine support in RESTEasy Reactive will be present in the first Alpha of Quarkus 2.0 (see the linked PR for details). That Alpha will hopefully land next week.
2 more CI jobs to finish and then I can merge. Hopefully in the next hour or so.
@akoufa Until RESTEasy Reactive includes coroutines support. I used your guidance to create the following "scope" function to adapt resource methods.
fun <T, R> T.resourceScope(block: suspend T.() -> R): Uni<R> = GlobalScope.async(Infrastructure.getDefaultExecutor().asCoroutineDispatcher()) { block.invoke(this@resourceScope) }.asUni()
It's used as...
class TestCoroutineResource { fun test(): Uni<String> = resourceScope { ... } }
Is this considered to be efficient or is it dispatching to worker threads and waiting on them? Other than knowing the executor is the Mutiny default executor I have no idea if that's the executor Quarkus uses internally.
@kdubb What do you think of the following? This should be much more efficient because we dont dispatch to worker threads and stay on the Vert.x event loop.
import io.smallrye.mutiny.Uni
import io.smallrye.mutiny.coroutines.asUni
import io.vertx.core.Context
import io.vertx.core.Vertx
import kotlinx.coroutines.*
import java.util.concurrent.AbstractExecutorService
import java.util.concurrent.TimeUnit
import javax.enterprise.context.ApplicationScoped
import javax.inject.Inject
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.ws.rs.core.MediaType
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
class VertxCoroutineExecutor(
private val vertxContext: Context
) : AbstractExecutorService() {
override fun execute(command: Runnable) {
if (Vertx.currentContext() != vertxContext) {
vertxContext.runOnContext { command.run() }
} else {
command.run()
}
}
override fun shutdown(): Unit = throw UnsupportedOperationException()
override fun shutdownNow(): MutableList<Runnable> = throw UnsupportedOperationException()
override fun isShutdown(): Boolean = throw UnsupportedOperationException()
override fun isTerminated(): Boolean = throw UnsupportedOperationException()
override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean = throw UnsupportedOperationException()
}
@ApplicationScoped
class MyScope : CoroutineScope {
override val coroutineContext: CoroutineContext = SupervisorJob()
fun <T> asyncUni(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Uni<T> {
val vertxContext = checkNotNull(Vertx.currentContext())
val dispatcher = VertxCoroutineExecutor(vertxContext).asCoroutineDispatcher()
return async(context + dispatcher, start, block).asUni()
}
}
@ApplicationScoped
@Path("/")
class MyResource @Inject constructor(
private val scope: MyScope
) {
@GET
@Produces(MediaType.TEXT_PLAIN)
@Path("test")
fun test(): Uni<String> = scope.asyncUni {
"Hello ${Thread.currentThread().name}"
}
@GET
@Produces(MediaType.TEXT_PLAIN)
@Path("test2")
fun test2(): Uni<String> = scope.asyncUni {
callLongRunningTask()
}
private suspend fun callLongRunningTask() = withContext(Dispatchers.Default) { // In real applications this should be the managed executor service from Quarkus (see https://quarkus.io/guides/context-propagation#usage-example-for-completionstage).
delay(3000)
"Done!"
}
}
Description
Hello to all. I am lately heavily using Quarkus together with Kotlin and I would like to ask you what the progress is, if any, regarding Kotlin Coroutines support in Quarkus APIs. Providing suspendable function APIs, like for example my other feature request, will improve the Quarkus experience for Kotlin developers. Together with the upcoming Hibernate Reactive this would allow us write synchronous-like non blocking code end to end. As Quarkus is based on Vert.x which already has superb Kotlin Coroutines support that is the way to go for Quarkus Kotlin developers. Also Kotlin Coroutines are a compile time construct which do not hinder adopting Project Loom in the future.
Nevertheless I was following the issues in GraalVM Repository and I think the blockers regarding native mode support should be fixed now: https://github.com/oracle/graal/issues/366 https://github.com/oracle/graal/issues/1330 .