How to enforce that a function should be called with a coroutine? - kotlin

Is there a way to make it so that a function has to be called with a coroutine in an IO scope?
I guess I can do
suspend fun f() {}
But maybe there is an annotation for this?
What if I still want the function to be blocking? Because inside the function I am usually doing db calls.

If it's a suspend function, it will certainly only be callable from coroutines.
As for requiring it to be called in an IO scope, you could just install the IO dispatcher yourself:
suspend fun f() = withContext(Dispatchers.IO) { ... }
...or, if you really wanted to just reject any user who wasn't using the IO dispatcher, you could write require(coroutineContext[CoroutineInterceptor] == Dispatchers.IO), but I'd certainly find that very strange as a user.

Related

Proper way of dealing with blocking code using Kotling coroutines

Suppose I have a blocking function because of some third party library. Something along these lines:
fun useTheLibrary(arg: String): String {
val result = BlockingLibrary.doSomething(arg)
return result
}
Invocations to BlockingLibrary.doSomething should run on a separate ThreadPoolExecutor.
What's the proper way (assuming there is a way) of achieving this with kotlin?
Note: I've read this thread but seems pretty outdated
If the blocking code is blocking because of CPU use, you should use Dispatchers.Default. If it is network- or disk-bound, use Dispatchers.IO. You can make this into a suspending function and wrap the blocking call in withContext to allow this function to properly suspend when called from a coroutine:
suspend fun useTheLibrary(arg: String): String = withContext(Dispatchers.Default) {
BlockingLibrary.doSomething(arg)
}
If you need to use a specific ThreadPoolExecutor because of API requirements, you can use asCoroutineDispatcher().
val myDispatcher = myExecutor.asCoroutineDispatcher()
//...
suspend fun useTheLibrary(arg: String): String = withContext(myDispatcher) {
BlockingLibrary.doSomething(arg)
}
If your library contains a callback-based way to run the blocking code, you can convert it into a suspend function using suspendCoroutine() or suspendCancellableCoroutine(). In this case, you don't need to worry about executors or dispatchers, because it's handled by the library's own thread pool. Here's an example in the Retrofit library, where they convert their own callback-based API into a suspend function.

when the thread will suspend a suspend-function?

I am just learning about Kotlin's Coroutines, and I'm curious when the thread will suspend a suspend-function. In Golang, we know it will suspend a goroutine in several cases; calling another goroutine, block-syscall, channeling, gc. Is Kotlin's coroutine the same?
In Kotlin the rule is extremely simple: a coroutine never suspends on its own, you must explicitly write code to make it suspend. In most usage scenarios this code is buried within the functions you call, though. Here's a simple example:
val result = suspendCoroutine { continuation ->
makeAsyncCall(onComplete = { continuation.resume(it) })
}

Should be used a CoroutineScope's extension function or a suspending function

I'm writing an app using coroutines (code below is greatly simplified). Recently I've watched Coroutines in Practice talk and got a little confused. Turns out I don't know when to use a CoroutineScope's extension function and when to use a suspending function.
I have a mediator (Presenter/ViewModel/Controller/etc) that implements CoroutineScope:
class UiMediator : CoroutineScope {
private val lifecycleJob: Job = Job()
override val coroutineContext = lifecycleJob + CoroutineDispatchersProvider.MAIN
// cancel parent Job somewhere
fun getChannel() {
launch {
val channel = useCase.execute()
view.show(channel)
}
}
}
Business logic (Interactor/UseCase):
class UseCase {
suspend fun execute(): RssChannel = repository.getRssChannel()
}
And a repository:
class Repository {
suspend fun getRssChannel(): RssChannel {
// `getAllChannels` is a suspending fun that uses `withContext(IO)`
val channels = localStore.getAllChannels()
if (channels.isNotEmpty()) {
return channels[0]
}
// `fetchChannel` is a suspending fun that uses `suspendCancellableCoroutine`
// `saveChannel` is a suspending fun that uses `withContext(IO)`
return remoteStore.fetchChannel()
.also { localStore.saveChannel(it) }
}
}
So I have a few questions:
Should I declare Repository#getRssChannel as a CoroutineScope's extension function (because
it spawns new suspending functions: getAllChannels,
fetchChannel, saveChannel)? How can I use it in the UseCase then?
Should I just wrap a Repository#getRssChannel into a
coroutineScope function in order to make all spawned suspending
functions to be children of the latter?
Or maybe it's already fine and I should change nothing. When to
declare a function as a CoroutineScope's extension then?
A suspending function should return once it has completed its task, it executes something, possibly taking some time while not blocking the UI, and when it's done it returns.
A CoroutineScope extension function is for a fire-and-forget scenario, you call it, it spawns a coroutine and returns immediately, while the task continues to execute.
Answer to question 1:
No, you should not declare Repository#getRssChannel as an extension function of CoroutineScope, because you only invoke suspend functions but not start (launch/ async) new jobs. As #Francesc explained extension function of CoroutineScope should only start new jobs, but cannot return immediatly result and should not be declared as suspend by itself.
Answer to question 2:
No, you should not wrap Repository#getRssChannel into a CoroutineScope. Wrapping makes only sense if you start (launch/ async) new coroutines in this method. The new jobs would be children of the current job and the outer method will only return after all parallel jobs are finished. In your case you have sequential invocations of other suspending coroutines and there is no need of a new scope.
Answer to question 3:
Yes, you can stay with your code. If you would need the functionality of UiMediator#getChannel more then once, then this method would be a candidate of an extension function for CoroutineScope.

Convey intended thread type (IO, default, main) when declaring suspend function

When designing an API with a suspend function, sometimes I want to convey that this function should be called on, say, an IO thread. Other times that it is essential to do so.
Often it seems obvious; for example a database call should be called using Dispatchers.IO but if it's an interface function, then the caller cannot assume this.
What is the best approach here?
If the suspend function really must run in a specific context, then declare it directly in the function body.
suspend fun doInIO() = withContext(Dispatchers.IO) {
}
If the caller should be able to change the dispatcher, the function can add the dispatcher as a default parameter.
suspend fun doInIO(context: CoroutineContext = Dispatchers.IO) = withContext(context) {
}
There is no strict mechanism for contracts like that, so you are flexible with choosing the mechanism that suits you and your team.
1) Always use withContext(Dispatcher.IO). This is both strict and performant, if a method is invoked from within IO context it will be fast-path'ed.
2) Naming/annotation-based conventions. You can make an agreement in the team that any method which ends with IO or has a specific annotation should be invoked with Dispatchers.IO. This approach works mostly in small teams and only for project-private API. Once you start exporting it as a library/module for other teams such contracts tend to be broken.
3) You can mix the previous approach with a validation:
suspend fun readFile(file: ...) {
require(coroutineContext[ContinuationInterceptor] == Dispatcher.IO) {
"Expected IO dispatcher, but has ${coroutineContext[ContinuationInterceptor]} instead"
}
// read file
}
But this validation works only if you are not wrapping IO dispatcher in some kind of delegate/proxy. In that case, you should make validation aware of such proxies, something like:
fun validateIoDispatcher(dispatcher: ContinuationInterceptor) {
if (dispatcher is Dispatchers.IO) return
if (dispatcher is ProjectSpecificIoDispatcher) return
if (dispatcher is ProjectSpecificWrapperDispatcher) {
validateIoDispatcher(dispatcher.delegate)
} else {
error("Expected IO dispatcher, but has $dispatcher")
}
}
I want to convey that this function should be called on, say, an IO thread. Other times that it is essential to do so.
Not sure what the difference is between "should" and "essential", but having these approaches in mind you can combine it with default method parameters such as suspend fun probablyIO(dispatcher: CoroutineDispatcher = Dispatchers.IO) or more flexible naming/annotation conventions.

does IO in coroutines cause suspension?

Inside a coroutine I am doing a http-request with OkHttpClient. The request is done from a function that has the suspend keyword:
suspend fun doSomethingFromHttp(someParam:String): Something {
...
val response = HttpReader.get(url)
return unmarshalSomething(response)!!
}
I assume that the function can be suspended on entry since it has the suspend keyword, but will the coroutine also be suspended when doing the http-request? What about other kinds of blocking IO?
There's no automagic going on with Kotlin coroutines. If you call a blocking function like HttpReader.get(), the coroutine won't be suspended and instead the call will block. You can easily assure yourself that a given function won't cause the coroutine to suspend: if it's not a suspend function, it cannot possibly do it, whether or not it's called from a suspend function.
If you want to turn an existing blocking API into non-blocking, suspendable calls, you must submit the blocking calls to a threadpool. The easiest way to achieve it is as follows:
val response = withContext(Dispatchers.IO) { HttpReader.get(url) }
withContext is a suspend fun that will suspend the coroutine, submit the provided block to another coroutine dispatcher (here IO) and resume when that block is done and has come up with its result.
You can also easily instantiate your own ExecutorService and use it as a coroutine dispatcher:
val myPool = Executors.newCachedThreadPool().asCoroutineDispatcher()
Now you can write
val response = withContext(myPool) { HttpReader.get(url) }
This PR has example code for proper OkHttp coroutines support
https://github.com/square/okhttp/pull/4129/files
It uses the thread pools of OkHttp to do the work. The key bit of code is this generic library code.
suspend fun OkHttpClient.execute(request: Request): Response {
val call = this.newCall(request)
return call.await()
}
suspend fun Call.await(): Response {
return suspendCancellableCoroutine { cont ->
cont.invokeOnCancellation {
cancel()
}
enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
if (!cont.isCancelled) {
cont.resumeWithException(e)
}
}
override fun onResponse(call: Call, response: Response) {
if (!cont.isCancelled) {
cont.resume(response)
}
}
})
}
}
There are two types of IO libraries in JAVA world, using IO or NIO.
You can find more documentation at https://dzone.com/articles/high-concurrency-http-clients-on-the-jvm
The ones using NIO, can theoretically provide true nonblocking suspension unlike IO ones which only offload the task to a separate thread.
NIO uses some dispatcher threads in the JVM to handle the input output sockets using multiplexing (Reactor design pattern). The way it works is, we request the NIO/dispatchers to load/unload something and they return us some future reference. This code can be turned into coroutines easily.
For IO based libraries, coroutine implementation is not true non blocking. It actually blocks one of the threads just like in Java, however the general usage pattern is, to use Dispatcher.IO which is a threadpool for such blocking IO tasks.
Instead of using OkHttpClient, I would recommend using https://ktor.io/docs/client.html