Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kotlin Coroutines in Quarkus #10162

Closed
akoufa opened this issue Jun 22, 2020 · 23 comments · Fixed by #16489
Closed

Kotlin Coroutines in Quarkus #10162

akoufa opened this issue Jun 22, 2020 · 23 comments · Fixed by #16489
Assignees
Labels
area/kotlin kind/enhancement New feature or request
Milestone

Comments

@akoufa
Copy link

akoufa commented Jun 22, 2020

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: oracle/graal#366 oracle/graal#1330 .

@akoufa akoufa added the kind/enhancement New feature or request label Jun 22, 2020
@geoand
Copy link
Contributor

geoand commented Jun 22, 2020

cc @evanchooly

@evanchooly
Copy link
Member

We're working on a formal timeline but unofficially here's my short term plan:

  1. Finish the panache support (currently wrapping up the mongodb-panache support to mirror the JPA support).
  2. Make sure dev mode works properly for kotlin projects (there's a couple of issues on this and one PR underway that might clear them all up at once)
  3. It's not set in stone yet, but the kotlinx serialization was likely the next target.

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: #8194

@evanchooly
Copy link
Member

Also, my unordered, unprioritized working list: area/kotlin

@emmanuelbernard emmanuelbernard added this to Todo in Kotlin roadmap via automation Jun 30, 2020
@emmanuelbernard
Copy link
Member

@cescoffier can you CC Arthur so he can put some progress as he finds stuff on the native image compilation for Kotlin co-routines

@emmanuelbernard
Copy link
Member

Does it make any conceptual sense that Vert.x has a Kotlin API generator, and whether we should extend this for Quarkus @cescoffier

@cescoffier
Copy link
Member

@Tas-Hur <-- That should be Arthur

@anavarr
Copy link
Contributor

anavarr commented Jul 1, 2020

Hi, I checked out the fix that allows the compiler to solve issues with irreducible loops : oracle/graal@4662877
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

@rainmanhhh
Copy link

webflux works with suspend fun now. maybe you can refer to it?

@akoufa
Copy link
Author

akoufa commented Sep 18, 2020

Hello to all. Any updates on supporting Kotlin Coroutines ?

@JoaaoVerona
Copy link

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...

@cescoffier
Copy link
Member

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

@evanchooly
Copy link
Member

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.

@evanchooly
Copy link
Member

now dev mode might be another issue but I think that's literally the next item on my list to tackle.

@akoufa
Copy link
Author

akoufa commented Oct 20, 2020

@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.

@sherl0cks
Copy link
Contributor

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

@akoufa
Copy link
Author

akoufa commented Jan 14, 2021

@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 ?

@heubeck
Copy link
Contributor

heubeck commented Jan 16, 2021

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.

@akoufa
Copy link
Author

akoufa commented Jan 16, 2021

@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.

@geoand
Copy link
Contributor

geoand commented Mar 30, 2021

@evanchooly is looking into adding proper Corroutine support into RESTEasy Reactive, so watch this space for updates :)

@kdubb
Copy link
Contributor

kdubb commented Apr 16, 2021

@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.

@geoand
Copy link
Contributor

geoand commented Apr 16, 2021

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.

@evanchooly
Copy link
Member

2 more CI jobs to finish and then I can merge. Hopefully in the next hour or so.

Kotlin roadmap automation moved this from Todo to Done Apr 16, 2021
@quarkus-bot quarkus-bot bot added this to the 2.0 - main milestone Apr 16, 2021
@mschorsch
Copy link

mschorsch commented Apr 18, 2021

@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!"
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/kotlin kind/enhancement New feature or request
Projects
Development

Successfully merging a pull request may close this issue.