In Spring WebFlux, when dealing with blocking IO, ...
# coroutines
c
In Spring WebFlux, when dealing with blocking IO, I assume the correct dispatcher to use is Dispatchers.IO. However, when using non-blocking IO, should we be using Default or Unconfined? I’m particularly thinking about async blocks like below.
Copy code
@GetMapping("/things")
    suspend fun getThings() = coroutineScope {
        val thing = async {
            suspendingCall()
        }

        val anotherThing = async {
            suspendingCallAgain()
        }

        listOf(thing.await(), anotherThing.await())
    }
l
Let
Default
be the default is probably the safest.
c
Default may be safe, but so too might unconfined. I’m looking to understand more about how unconfined works and whether there is a way to use the underlying thread pool used in Webflux rather than the one used in Dispatchers.Default.
z
It shouldn't matter to the non-blocking IO call from which dispatcher you call it. In this case, where you're basically just aggregating calls, I think the best option is to be unopinonated (don't specify a dispatcher at all). As a general rule, only specify a dispatcher explicitly if you actually care what thread your code runs on. If all you're doing is calling other suspend functions, it shouldn't matter.
Reasons: 1) If it doesn't matter, you don't need to. No point in writing code that you don't need to write. 2) It makes your code more complex and might be harder to read. E.g. future readers might look at it and wonder what the intent of switching dispatchers was when it seems like it doesn't have any real effect. 3) Switching dispatchers is not free. There are a lot of optimizations in the coroutine runtime that skip doing a bunch of work if the contexts match, so if you avoid changing contexts unnecessarily you might get some free performance benefit.
c
I think I’m understanding a bit better now. In my example, the suspend function will adopt the context from the scope of whatever calls it (namely the spring webflux internals). If that scope already has a context with a particular dispatcher, then unless I specify one, it will already be adopting it. I had the impression that by not setting it, it was being overwritten with Dispatchers.Default, but in actual fact that only happens if there is no dispatcher.
z
Correct. One way to think about it is that since
async
is an extension on
CoroutineScope
, it automatically inherits everything from the calling scope. The behavior you were initially expecting is what you got if you used
GlobalScope.async
, which is strongly discouraged for a number of reasons, including this.
c
Awesome. Thanks for taking the time to explain 👍 .