Kotlin + Arrow-kt - why are my coroutines not cancelling properly? - kotlin

I am using Kotlin together with Arrow-Kt libraries.
I am launching on a specific scope some coroutines that make use of Arrow-kt's Schedule.
At a certain time, I want to be able to cancel all those coroutines that were launched on that scope, but after I cancel the scope basically nothing changes and whatever was running on the Schedule, continues to run, which is not what I wanted.
I already tried to place some yield() calls to force the coroutines to be cancellable, but the behavior didn't change.
Here is the code:
Main function doing the launches:
private val ballFetchingScope= CoroutineScope(CoroutineName("ball-fetching"))
fun fetchBalls(periodicity: Periodicity) {
val balls = stockRepository.getAllBalls() //basically a list of different balls
balls.forEach {
ballFetchingScope.launch(Dispatchers.IO) { ballFetcher.startFetching(it, periodicity) }
}
}
startFetching function, using Arrow-kt's schedule:
suspend fun startFetching(ball: Ball, periodicity: Periodicity) {
Schedule.forever<Unit>()
.and(Schedule.spaced(periodicity))
.repeat {
yield()
//ball fetching logic here
}
}
Expected behavior:
When calling ballFetchingScope.cancel() all coroutines are cancelled and all fetching stops.
Ideally not even needing to wait until it reaches the yield() call, if it is waiting for the next run to happen, I would like for it to cancel and not even start a new run of the repeat block.
What is actually happening:
Fetching continues to happen normally.

It's a bit hard to say here what is going on.
It's very strange that this is not working for you, since Schedule relies on KotlinX kotlin.coroutines.delay to execute the Schedule.spaced. So it should get cancelled while waiting for the next run.
It also checks coroutineContext.ensureActive() before running the function passed to repeat so it also automatically check in the place where you now manually placed yield.
Which version of Arrow are you using? And could you share a fully reproducible example?

I am answering my own post just to make sure you are not making the same dumb mistake I was making.
Everything was working as supposed, the issue was on my side.
I had two different instances of the class containing the ballFetchingScope
This means that I was calling the ballFetchingScope.cancel() on the scope for one of those instances while the Coroutines were running on the scope in the other instance.
Botom-line: Make sure you are not using multiple instances when you think you have only one.

Related

Kotlin co-routine executes sequentially, but only on production machine

I have a bunch of network requests I want to conduct in parallel.
The following pseudo code should give a good idea of what I'm doing right now:
runBlocking {
buildList {
withContext(tracer.asContextElement()) {
items.forEach { item ->
add(
async {
// a few IO intensive operations (i.e. network requests)
}
)
}
}
}.awaitAll()
}
I have tracing tools set up and locally this seems to do the job. In my production infrastructure however the async tasks execute sequentially, i.e. the second one starts immediately after the first one finishes.
I have also tried using withContext(Dispatchers.IO.plus(tracer.asContextElement())) but I observe no difference.
The only thing I can say is that my development machine has multiple CPU cores, and my production machine will normally have 1. Regardless, due to the IO heavy nature of these processes I doubt this is the problem. I can't really explain what is causing this, but my gut feeling is that I'm fundamentally not understanding something about how Coroutines work in Kotlin.
As to the nature of the network request in question, I'm using a third party SDK that asynchronously executes the request, and seems to use ForkJoinPool.commonPool() under the hood as an executor.
If you don't switch dispatchers here, all those coroutines will run in the same thread - the one blocked by runBlocking. If the computation inside each coroutine is blocking, they will block the only thread one by one without any way to parallelize. This would explain what you're seeing (although it's strange that you don't reproduce locally).
I have also tried using withContext(Dispatchers.IO.plus(tracer.asContextElement())) but I observe no difference.
Your fix should work, unless the IO you're performing is actually managing threads itself and also confining the execution to a single thread no matter where it's called from. Maybe you should look into the actual IO then.
EDIT: you mentioned that you perform the IO operations via a third party SDK that uses the common ForkJoinPool - this one is backed by a single thread on a single-CPU machine, so this explains why the calls aren't parallelized in your single-CPU production machine. The only options to fix that would be:
check whether the SDK you're using allows to customize the backing pool of threads
customize the size of the ForkJoinPool using the JVM property java.util.concurrent.ForkJoinPool.common.parallelism
use another SDK :)
You still need to customize the dispatcher in addition to that if you're calling the library in a blocking way, but not if you're converting their async tasks into suspensions using Future.await() or similar.
Now, a few other things to note in this code:
you don't need buildList { .. }, you can just use map { thing } instead of forEach { add(thing) } and you'll get the resulting list as a return value (it also works across withContext, because it returns the lambda result)
withContext actually waits for all child coroutines to finish, so awaitAll() is misplaced here (it should rather be inside withContext)
actually, you probably don't need withContext at all, you can pass the custom context directly to runBlocking, unless you have other things in runBlocking that you don't want to run in this context
(optional) if the IO computations don't return results, you don't need awaitAll at all, and you could just use launch instead.
Assuming you do need the result, so ignoring the last point, your current code (with dispatcher fix) could be rewritten to:
val results = runBlocking(Dispatchers.IO + tracer.asContextElement()) {
items.map { item ->
async {
performIO(item)
}
}.awaitAll()
}
Otherwise:
runBlocking(Dispatchers.IO + tracer.asContextElement()) {
items.map { item ->
launch {
performIO(item)
}
}
}

How to write request and response code with Kotlin Flows?

Problem Description
My job is very async IO heavy so a lot of what we do is requesting a value and then listening for the response. Something like
connection.send(GetServices(...)))
connection.receive<GetServicesResponse>()
Now if I did this in Kotlin with suspend functions, I could get incorrect results because the message might have been received to quickly.
connection.send(GetServices(...)))
// Recevied GetServiceResponse Here <---------------
connection.receive<GetServicesResponse>()
// Times out because it never got to see the response
However, if I flip it now I will never send the request lol. So that just straight up won't work.
connection.receive<GetServicesResponse>() // Timeout because we never send the request
connection.send(GetServices(...)))
So then you go "OK I will just launch/async" but no matter which way you do that you still have this problem that you can never be sure the listener is actually listening. There is no way to tell that a suspend function is at the point it should be and is listening.
val response = async { connection.receive<GetServicesResponse>() }
connection.send(GetServices(...)))
response.await()
This can still fail because when we call async it doesn't guarantee the job has ran. So the job could still be scheduled to run by the time we receive the request.
launch { connection.send(GetServices(...))) }
connection.receive<GetServicesResponse>()
This can still fail because when we call launch it could run almost immediately if there are not a lot of jobs and multiple CPUs. Meanwhile, the thread running this code could get suspended by the OS. Kotlin Coroutines are nice but the OS can still stop any thread it feels like.
To fix this, I use UNDISPATCHED so that I guarantee that a launch runs until it suspends.
val response = async(start = CoroutineStart.UNDISPATCHED) {
connection.receive<GetServicesResponse>()
}
connection.send(GetServices(...)))
response.await()
This works but only in simple cases. If an engineer does something that causes a suspend before the listen then I am right back to same problem. The code above is both an example of working and not working code at the same time depending on the implementation of connection.receive. This gets worse when I start trying to use flows to receive data. Flow operations like merge or launchIn end up launching coroutines. So you can have coroutines launching coroutines so something like UNDISPATCHED doesn't appear to be sufficient. The only reason I know that is I actually tested it. Then again my code could be wrong.
Question
So the question is how do I guarantee listening? It seems like I can't with Kotlin Coroutines and flows?
Attempts
It seems like with RxJava I could, because I know when subscribe is called then it went up the entire chain. Once subscribe returns, that Observable is live. However, flows do not work like that in this regard. collect aka subscribe both suspends and eventually the flow starts listening so you have no way to know for sure.
I have thought of literally sending a "START" element on a flow to say it is live. However, you can get into the exact same situation.
flow {
emit("START")
emitAll(realFlow)
}
The OS can suspend my thread between the "START" and the emitAll(realFlow).
chanelFlow {
launch { realFlow.collect { send(it) } }
send("START")
}
Is right back to the same problem above. The job might not have run. So you launch undispatched.
chanelFlow {
launch(start = CoroutineStart.UNDISPATCHED) {
realFlow.collect { send(it) }
}
send("START")
}
But again, this is brittle. For all I know the realFlow has merges of it's own that are going to be scheduled and not executed. This has almost lead me back to using listeners. With a listener, I know I added them to the list of other listeners. No suspension. That seems like a huge step backwards and would make me wonder why I didn't just use RxJava.
If you got to the end. Thank you for reading my problem. I appreciate any attempt to help me.
I have been facing this problem several times as well. If the API is designed this way, there is unfortunately little you can do about it.
Just like you I ended up using UNDISPATCHED in simple cases where I knew one suspension point would be sufficient:
val response = async(start = CoroutineStart.UNDISPATCHED) {
connection.receive<GetServicesResponse>()
}
connection.send(GetServices(...)))
response.await()
When you need to make sure it can go through several suspension points, there is always the option of adding a small delay, but it's dirty, slow and doesn't 100% guarantee anything either:
val response = async {
connection.receive<GetServicesResponse>()
}
delay(50)
connection.send(GetServices(...)))
response.await()
But honestly the best is to have a better API. For instance, in my own STOMP library Krossbow, I have designed the subscribe() as suspend functions that return Flow. It might be unconventional but the API guarantees that after resuming the subscription has been made. The user can then send a request, and collect the Flow afterwards anyway without losing events.

How can I force update UI before program flow continues?

For example, for the following code:
OpenOutputWindow()
Sleep(2)
The output window only opens AFTER sleeping for 2 seconds. Why does this happen and how does one work around it? Do I need multiple threads?
Yes, your assumption is correct. UI updates of the software are generally performed on the "main thread" of the application. A script is, by default, also run on the main-thread and hence blocks those updates until some CPU cycles are made available for this.
Sometimes this can be achieved by calling dedicated Update commands (such as UpdateImage() or ShowImage() etc.) but this does not work for the text-output in the output window.
If the script is run on a separate thread, then you don't see this.
You can put a script into a separate thread like this (old method):
// $BACKGROUND$
OpenOutputWindow()
ClearResults()
Result("\nbefore sleep")
Sleep(2)
Result("\nafter sleep")
Note that the first line needs to be exactly like this, including the space and capitalization. It also needs to be the first line.
or you can do it like that (new, object oriented way):
class CMyClass
{
void MyMethod(object self)
{
OpenOutputWindow()
ClearResults()
Result("\nbefore sleep")
Sleep(2)
Result("\nafter sleep")
}
}
StartThread( Alloc(CMyClass), "MyMethod" )

Can function of DAO class be called in ViewModel class without Coroutine

All the queries to the database are written in CouroutineScope. But this line of code in just inside of ViewModel class. And it's not blocking the UI thread. I don't understand how is it executing
private val nights = database.getAllNights()
You can see the whole repo in this link
The Dao function getAllNights() is returning a LiveData:
fun getAllNights(): LiveData<List<SleepNight>>
This return is done immediately upon calling that function, without waiting for the results to load from the database. The LiveData can then be observed to be notified when the data is loaded (on a background thread, asynchronously). It will also notify observers if the result of the query changes later on.
In contrast, if you take one of the calls where it's wrapped inside a launch and a switch to the IO dispatcher, such as getTonight():
fun getTonight(): SleepNight?
A Dao function like this will block the thread until it gets the result of the query, which is why it's important that you take care of going to a background thread before calling it.

How to get current `coroutineContext` from a non-suspend function?

Say I have a logging function:
fun log(message: String)
Unfortunately, this function will be called both from coroutines and outside of coroutines. In case of coroutines, I would like to log additional information coming from coroutine context (for example, coroutine's name).
How can I achieve this?
These are my thoughts but I don't have a solution:
Somehow figure out inside log if it is inside a coroutine and get coroutineContext. Is this possible?
I could have two versions of log e.g. log and logSuspend. But how do I ensure the right one gets called? If I'm inside suspend, nothing prevents me from calling log by accident. Additionally, I may have a regular helper function. Which one should it call?
Maybe something with ThreadLocal, for example I could coroutineContext inside a ThreadLocal at some point, but how do I ensure it stays up to date?
Somehow figure out inside log if it is inside a coroutine and get
coroutineContext. Is this possible?
I don't think there is a good solution for that.
I would create two log functions, first for general purpose, second - an extension function on CoroutineScope for coroutines:
fun log(message: String) {...}
fun CoroutineScope.log(message: String) {
//here you can access coroutineContext
}
And if you call log function from the coroutine like this:
GlobalScope.launch {
log("CoroutineScope.log")
}
the extension function will be called and you will have access to coroutineContext.
Note: GlobalScope is not recommended to use, it is just for demonstration purposes.