Non-gracefully cancel a heavy coroutine job in Kotlin?

Hi

here is a Kotlin example of my problem.

I pull a value from a computationally heavy generator, it takes too long, I decide to cancel this job, but this heavy stubborn job continues to load CPU and won’t die.

  1. It would be most nicely to have a solution based on coroutines, e.g. using withTimeout or withTimeoutOrNull.

  2. If not possible then based on (multi-)threading.

  3. If above not possible then :cry: using multiprocessing with external sys calls (platform.posix.system(), Runtime.getRuntime().exec(), etc)

1 Like

Your code has to cooperate in cancellations. You can’t force arbitrary code to just stop running, this is the same for Kotlin coroutines and for Java threads (Thread.interrupt() doesn’t necessarily stop the thread). Please see: https://kotlinlang.org/docs/cancellation-and-timeouts.html#cancellation-is-cooperative

1 Like

thank you for your fast reply @broot.

I’ve read this document section yesterday many times. From docs it is not very much clear how to make the job launcher and the deeper sequence generator cooperative. However, it looks like there are 2 approaches:

  1. use yield()
  2. use if(isActive==...)

Neither the first nor the second is straight forward if one addresses to inject them into the sequence generator f() in the example above. In both cases even the compiler complains a lot (which is actually very cool).

Here is my workaround for this issue currently.

If you could make it nicer (based on yield() or isActive or etc), please let me know!

Could you use flows instead of sequences? They are pretty similar, but the main difference is that flows are asynchronous and because of that they are more coroutine-friendly.

edit:
Ohh wait, only now I realized you don’t really return a sequence of items from f(), but only a single item. What is the point of using a sequence then? f could be just a suspend function and then you can easily react to cancellations.

But if this is only an example and in reality you actually stream multiple items, you don’t like the API of flows, because you would prefer to just pull items from the stream, then another alternative is to use channels. Create a channel, spin a coroutine that streams items to this channel and read on another end. Just make sure you close/cancel the coroutine when you don’t need it anymore.

1 Like

First of all many thanks for the hint with flows! :fire: They indeed look much more natural in my case. Hopefully, flow’s emit() will bring cooperativeness between the deeper emits and the top-level caller. Gonna try it.

P.S. Regarding f() – exactly, it is only an example. It was just a mock-up of my computationally heavy recursive function. For mocking up I’ve intentionally chosen Fibonacci function in its ineffective plain implementation to get exponential computational complexity in a few lines of code. If using .first() is a bit clumsy and distracting – sorry for that!

just realized that flows are non-iterable :cry:
Perhaps, one has to use channels and pipelines instead of plain flows to be able iterate the elements one-by-one?