we are running Spring servlet mvc with virtual thr...
# spring
b
we are running Spring servlet mvc with virtual threads on java 21, but some generated API clients come with coroutines; what's the recommended way to integrate those? you can't make the controller method suspend since we're not on webflux; do we need a runBlocking on the IO dispatcher? AFAIK that one goes back to the blocking model; is there a way to integrate that into the virtual thread?
s
There are kind of two questions: 1. How do you run a coroutine to invoke the suspending function, and 2. How do you wait for the suspending function to finish If you only care about 1️⃣, you have more options; you could make a coroutine scope or even just use
GlobalScope
, and use
launch()
. But if you also need 2️⃣, you're going to need to block the thread at some point. There's no getting away from that unfortunately. So
runBlocking
is the way to go. You probably don't even need the IO dispatcher at that point, since the Spring request will have its own dedicated worker thread which you're free to use for whatever you want during the request. Better to let the
runBlocking
function's event loop make use of that thread than just leave it idle while the IO dispatcher does the work.
I guess I didn't touch on the virtual threads part, though... that seems important, let me think
b
the generated API clients use KTOR so I hope that one uses the built in Java HttpClient which is able to suspend in a JVM virtual thread
s
Hah, it just gets more complicated at every turn 😄
b
I know there's a Dispatchers.LOOM context but not sure how that integrates with Spring
my guess is that that one just launches a virtual thread right away
s
I don't think you need to worry too much about what integrates with what. To get from your Spring controller method into a coroutine, you can for sure just use
runBlocking
. All the better if the blocked thread turns out to be a virtual thread.
As for the Ktor part, since it's all built with asynchronous suspending calls and non-blocking IO, it really shouldn't matter whether it's dispatched on a virtual thread or a real one. I'd just let it do whatever it defaults to, and not worry about threading.
A Loom-based coroutine dispatcher is useful when you want to make old-fashioned blocking calls from a coroutine, which is the same scenario where we currently use the existing
<http://Dispatchers.IO|Dispatchers.IO>
.
Ktor is non-blocking from the ground up, so neither
<http://Dispatcher.IO|Dispatcher.IO>
nor a loom dispatcher should be necessary since no threads (virtual or otherwise) will ever be blocked.
Does that help? I don't know if I'm explaining it well
b
I'm asking because https://www.youtube.com/live/szl3eWA0VRw?si=AfOgeybbfpZobAiN&amp;t=3536 shows you the runBlocking example and marks that as wrong
👀 1
there seem to be a lot of pitfalls on top with regards to spring thread context
the solution is "go with webflux"
which would be a pretty big change and I feel like going with a Java API client would be easier in that case
k
nice discussion
if I may hook, which dispatcher is spring using when using webflux?
b
I'm not sure, I suppose they are using their own
to integrate with their own event loop thingy
s
I watched the video you shared, and as I understand it they are talking about two problems: 1. Coroutines will not allow two blocking functions to run concurrently on a single virtual thread. 2. To enable concurrency between blocking calls, they introduce a dispatcher with multiple virtual threads. This means important thread-local context (e.g. security & logging context) is not available in the new coroutines. If you call suspending functions like the ones in Ktor, you won't have problem 1️⃣, because they are async functions which don't block. And if you just use
runBlocking
without a custom dispatcher, you'll stay on the original thread and won't run into problem 2️⃣. So in general, the problems in the video don't appear to apply to your situation. However, you should be aware that any suspending function can choose to use
withContext
internally to switch its dispatcher, which would potentially cause problem 2️⃣ to crop up further down the call stack in the Ktor internals. The video talks about how to mitigate this with extra coroutine context elements like
MDCContext
.
I too would be very curious to know how Spring actually invokes suspending functions in WebFlux. My first guess would be that it's as simple as using https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/mono.html to wrap it in a
Mono
, but perhaps there's more involved to set initial coroutine context and things like that.
b
so basically: use runBlocking in the controller method and don't worry about concurrency
s
That'd be my recommendation 👍. So long as you're calling suspending functions rather than blocking ones, you won't have the main problem they're describing in the video.
There are one or two other gotchas, like the thread context thing, but they're solvable.
b
do you lose the thread context if you don't change the dispatcher?
s
Assuming you use
runBlocking
and don't ever switch to a different dispatcher, then you'll always be on the same thread with the same thread context you started out with 👍.
b
ok, looks like the information in the video is outdated
s
Huh, I didn't know that!
Would have saved me a lot of time on a recent project 😄
b
runs in an unconfined dispatcher