https://kotlinlang.org logo
Title
d

Davio

02/13/2023, 7:36 AM
Hi all, I have some questions about Loom. When project Loom finally delivers all of its virtual thread goodness, is it expected that coroutines are going to be scheduled to run on virtual threads (letting the JVM thread scheduler pick actual threads) or is the Kotlin scheduler still going to pick the actual threads to run on?
r

reformator

02/13/2023, 8:10 AM
don't know about it. But I think when all Loom features will be delivered, Kotlin coroutines will become unnecessary.
d

Davio

02/13/2023, 8:11 AM
That is an opinion I hear a lot, but coroutines still offer a pretty nice API with its structured concurrency and using the Java VirtualThread API from Kotlin may not offer the best experience. So maybe we could just create a Loom dispatcher which schedules onto virtual threads instead of actual threads?
I found an example for this at https://kt.academy/article/dispatcher-loom which seemed quite promising
r

reformator

02/13/2023, 8:38 AM
Yes, structured concurrency is good. But everything which can be done with Kotlin coroutines, can be done with Loom. And it's not a rocket science to write a structured concurrency library for Loom.
d

Davio

02/13/2023, 8:52 AM
Maybe not, so we could expect that a wrapper library would be created eventually or maybe added to the Kotlin internal libraries
a

Adam S

02/13/2023, 9:02 AM
Kotlin Coroutines might be able to do everything that Loom can do, but Loom can’t do everything that Kotlin Coroutines can do, because Loom is limited to JVM only.
d

Davio

02/13/2023, 10:46 AM
Ah right, as my work mainly concerns Kotlin on the JVM I hadn't considered other platforms
But a Loom dispatcher seems like a pretty straightforward and non-intrusive way to create a bridge between coroutines and the way they are scheduled on actual threads
s

streetsofboston

02/13/2023, 1:07 PM
Here is another opinion:

https://youtu.be/1qezCNVWpHc

d

Davio

02/13/2023, 2:03 PM
So if we assume we have a servlet container such as Tomcat which switched from platform threads to using virtual threads, then each request is handled on a virtual thread and it doesn't matter if the code is blocking or not, because it just takes up 1 virtual thread and the JVM makes sure to find appropriate carrier threads to execute on. I do wonder though if a virtual thread can be suspended like a coroutine. From the Loom JEP:
When code running in a virtual thread calls a blocking I/O operation in the
java.*
API, the runtime performs a non-blocking OS call and automatically suspends the virtual thread until it can be resumed later.
This definitely sounds promising. So we still have the issue of parallelism, say I want to send 2 REST calls to other services at the same time and proceed when they're both done. From the same Loom JEP:
It is not a goal to offer a new data parallelism construct in either the Java language or the Java libraries.
So I guess we might end up with a hybrid model where:
runBlocking {
  val res1 = async { client1.getResponse() }
  val res2 = async { client2.getResponse() }
  res1.await() + res2.await()
}
is perfectly OK code. The runBlocking isn't a problem if it's just blocking a virtual thread (as long as it does not pin itself to a certain carrier thread).
r

reformator

02/13/2023, 2:09 PM
The analog for async in Loom is StructuredTaskScope
d

Davio

02/13/2023, 2:16 PM
Seems like a fun exercise to try and write a wrapper library for stuff like this; even though I'm pretty sure it either already exists or is worked on it might be a good exercise to get more knowledgeable about stuff like this
Here is a fun example of an implementation of a parallel map with Loom without coroutines:
fun main() {
    val list = listOf(1, 2)
    val result = list.parallelMap { it + 1 }
    println(result)
}

fun <T, R> List<T>.parallelMap(transform: (T) -> R): List<R> {
    StructuredTaskScope<Any>().use { scope ->
        val futures = this.map { item ->
            scope.fork { transform.invoke(item) }
        }

        scope.join()

        return futures.map { it.get() }
    }
}
We can use the aforementioned StructuredTaskScope, wrap it in Kotlin's version of the try-with-resources (which is 'use'). We then map the values into the list to a list of futures that we fork off with the scope. We join the scope which waits for all of the futures to complete and finally just use the return values of the futures by calling get(). Now, get() has always been a blocking call and that's why Futures existed for a brief time in Java 7 before being 'superseded' by CompletableFutures. Now that virtual threads are not blocking, it's funny to see they are reusing the old futures with their gets.
The only thing which irks me if we start combining Loom and Kotlin is that we might end up in situations where it's hard to know if we're running on a virtual thread or not. and thus whether it's safe to call blocking functions. At least with coroutines, we always know because we can just look at the suspend keyword or check if we are in a coroutine scope.
It might not end up being a big deal in actual applications, because if you're running a Spring Boot application with a servlet container which just parks requests on virtual threads, then you'll instinctively know you're always running on a virtual thread
j

Jarkko Miettinen

03/28/2023, 1:46 PM
On Adam's point about Loom not being available on non-JVM platforms, is there a way to use Kotlin coroutines in such a way that JVM virtual threads would be their carriers and that code in a lib would work on other platforms. They're a compile-time construct in after all and particular about the color of your function (
suspend
or not), which Loom is not.
s

streetsofboston

03/28/2023, 1:50 PM
I'd think you could have another JVM-only suppplied
Dispatcher
that would be backed by JVM's virtual threads.