Reputation: 182
I am calling a non-inlined library function, providing my own function f
that needs to suspend because it receives from a channel. When the library function is called I simply want to wait for it to complete in the current context. The library function will always call f
within its body (specifically, the library function does pre
, f
, then post
, where f
must be called between pre
and post
, so it could have been an inlined function). However within f
the outer coroutine context no longer applies.
My first thought was to surround the suspending call with runBlocking
, but this causes a deadlock, because the (potentially single) thread is now blocked until receive
completes, which prevents the producer from progressing.
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
// Some non-inlined library function that calls given function f immediately with some library provided value
fun libraryFunction(f: (Int) -> Int) =
f(5)
fun main() {
// Some outer loop that may sometimes only have 1 thread
runBlocking(newSingleThreadContext("thread")) {
// An existing channel
val channel = produce {
send("whatwewant")
}
// Our code
libraryFunction { libraryProvidedValue ->
println(libraryProvidedValue)
runBlocking {
println(channel.receive())
}
// A return value the library needs
7
}
}
}
What is the best way to solve this issue? Can using the inner runBlocking
be prevented?
In other words: is there anyway to pinky-promise that we are, in fact, still within the same coroutine in the lambda function?
As an additional illustration, the following works:
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
// Some non-inlined library function that calls given function f immediately with some library provided value
fun libraryFunction(f: (Int) -> Int) =
f(5)
fun main() {
// Some outer loop that may sometimes only have 1 thread
runBlocking(newSingleThreadContext("thread")) {
// An existing channel
val channel = produce {
send("whatwewant")
}
// <inline the start of the body of libraryFunction here> (which gives libraryProvidedValue)
// Our code
println(libraryProvidedValue)
println(channel.receive())
// <inline the end of the body of libraryFunction here>
}
}
Update: In this case, the only real issue seems to be that the compiler is not aware of the surrounding coroutine being the same in the lambda function body - but the code is entirely possible to run successfully if it did (as can be seen by inlining the library function). In essence, there is nothing wrong with the flow of this code in particular (because the library function calls the lambda function within its body), but it is a shortcoming in the lack of guarantees the compiler can determine. While runBlocking
makes the compiler happy, it has unwanted side effects (notably, the nested blocking part, which makes communication with the outside difficult due to the outer runBlocking
blocking up the potentially only thread).
Because of this, I decided to rewrite my entire code surrounding this in a style that uses Deferred
with an await
at the top level, instead of suspend
functions. I would regard this as very un-kotlin-ic, and it comes with potential problems of its own (like resource leaks), but it works for my scenario.
Note that this still does not answer the question posed in any way, but I wanted to note it as an alternative decision to make for future users faced with a similar problem.
Upvotes: 1
Views: 410
Reputation: 30695
Unfortunately there is only one way to achieve what you want - to use runBlocking
, but it has consequences like you described. suspend
functions should be called from a coroutine (runBlocking
in this case) or another suspend
function. So to achieve this without using runBlocking
libraryFunction
function must accept a suspend
f
function and be suspend
itself:
suspend fun libraryFunction(f: suspend (Int) -> Int) = f(5)
I would suggest first to receive the value from the channel, and then call the libraryFunction
:
runBlocking(newSingleThreadContext("thread")) {
// An existing channel
val channel = produce {
send("whatwewant")
}
// Our code
val receivedValue = channel.receive()
libraryFunction { libraryProvidedValue ->
println(libraryProvidedValue)
println(receivedValue)
// A return value the library needs
7
}
}
Upvotes: 1