I need to call suspendable methods from a single-t...
# coroutines
c
I need to call suspendable methods from a single-threaded library. Here is the context: ktor server -> standard suspendable Ktor routing methods -> Java single threaded templating rendering -(?)-> ORM suspendable methods with result Is it safe to use runBlocking (plus async/await inside) in this context? Something like:
Copy code
suspend fun someExternalSuspendFun(): String

fun returnResult(): String {
  return runBlocking {
    async {
      return@async someExternalSuspendFun()
    }.await()
  }
}
Is there a simpler way to do this?
e
Well for one the async await is useless, you could just call the suspending fn directly inside run blocking
k
You should not use run blocking within other suspending functions. From the docs:
Runs a new coroutine and blocks the current thread until its completion. This function should not be used from a coroutine. It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in main functions and in tests.
I dont quite grok exactly what you're trying to do, but it seems like you should be either: • Communicating over channels to a separate coroutine running the single threaded templating thing, or • Using
withContext(singleThreadedDispatcher)
c
@efemoney yes, thanks, it shoud work.
@kevin.cianfarini the single thread dispatcher will create a new thread, while runBlocking will not. In some cases it can be a problem to use it, but I don't think it is problematic here. If you think I should not, please explain me why.
k
You can store the single threaded dispatcher as a variable.
Copy code
val mySingleThreadedDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher()

...

withContext(mySingleThreadedDispatcher) { someTemplateStuff() }
Tbh it kind of sounds like you should be wrapping the API of the templating thing is a suspending interface with handles thread semantics internally.
Copy code
class SuspendingTemplateThing(private val templateThing: TemplateThing) {
  private val singleThreadContext = Executors.newFixedThreadPool(1).asCoroutineDispatcher()

  suspend fun doAThing() = withContext(singleThreadContext) { templateThing.doAThing() }
}
This ensures your suspending functions are main safe.
@Claude Brisson also the docs specifically call out not using
runBlocking
from a suspend function because iirc it can cause deadlock. If the docs mention you shouldn't do it, I doubt your use case is safe from the reasons they warn against using it. Furthermore,
runBlocking
will block the current thread. If you happen to be calling the templating thing from multiple different coroutines all running on different threads (such as with
<http://Dispatchers.IO|Dispatchers.IO>
) then each runBlocking call to the templating thing would happen on more than one thread.
n
It's hard to tell from the description if using
runBlocking
is safe or not. The warning against using it may be accurate or this could be exactly the scenario that
runBlocking
was designed for. It really depends on what code is calling
returnResult
, the thread that it's runnng on. If
returnResult
is a callback from a Java class/library that calls its callbacks from a dedicated thread for callbacks and expects callbacks to block, then you are using
runBlocking
as intended. There is a gotcha even in the intended use-case that if it's possible for other code to post to that thread then a dispatcher could exist that posts to that thread and then you could potentially deadlock (
runBlocking
calls suspend method that uses said dispatcher that queues coroutine to thread blocked by
runBlocking
).
k
If the non-suspending function
returnResult
above is run within a coroutine, which I believe it is since this is all happening in the context of a Ktor request, then runBlocking is not safe to use I don't believe.
n
Not necessarily. Consider:
Copy code
// suspending wrapper of non-suspend API
suspend fun SingleThreadedClass.printStuff(getMessage: suspend () -> String) {
    suspendCoroutine { cont ->
        printStuff(
            //Using runBlocking here:
            getMessage = { runBlocking { getMessage() }},
            onDone = { cont.resume(Unit) }
        )
    }
}

class SingleThreadedClass() {
    private val executor = ThreadPoolExecutor(0, 1, 0, TimeUnit.MILLISECONDS, LinkedBlockingQueue())
    
    /**
     * Executes block on private background thread to determine what to print.
     */
    fun printStuff(getMessage: () -> String, onDone:() -> Unit) {
        executor.execute {
            println(getMessage())
            onDone()
        }
    }
}
While
runBlocking
is being used from code that is part of a coroutine, it is not being called from a thread that is part of dispatching coroutines. When the docs say "This function should not be used from a coroutine" it means that it should not be called if a suspend function is on the call-stack. When execution moves to the private executor thread, there's no coroutine on the callstack . This sort of situation where a callback is called from a private thread(pool) is exactly what
runBlocking
is intended for. I could see this sort of situation being the case depending on the details of the "Java single threaded templating rendering" but it depends.
k
I took
Java single-threaded template rendering
to mean that the templating engine isn't thread safe and must be confined to a single thread. In your above example if the templating engine is a worker of sorts then yes, I believe you're correct, runBlocking would be fine. Regardless it seems like it might be easier to decouple these two concerns. Separately fetch the DB data and pass that data to the templating engine and you wouldn't need to worry about concurrency semantics here.