https://kotlinlang.org logo
#coroutines
Title
# coroutines
a

Azur Haljeta

12/11/2020, 8:18 PM
They say that coroutine cancellation is cooperative. That's perfect when it's possible to check for it via
isActive
. But how to make it cooperative when you're dealing with code which doesn't include loops, i.e. when you don't have a right place to put the
isActive
check?
A simple example is a blocking call with OkHttp3:
Copy code
suspend fun fetchData(): Int = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
    val client = OkHttpClient()

    val request = Request.Builder()
        .url("<http://slowwly.robertomurray.co.uk/delay/3000/url/http://www.google.co.uk>")
        .build()

    // the url uses a service which delays requests on purpose

    val response = client.newCall(request).execute()
    println("Got response ${response.code}")

    response.code
}
How do you make this code cooperative in terms of cancellation? This
withTimeout
below makes no sense since the code above isn't cooperative. The http request needs at least 3000 ms to complete and the timeout throws an exception after those 3 seconds, when it's too late already.
Copy code
GlobalScope.launch {
    withTimeout(2000L) {
        fetchData()
    }
}
I know that OkHttp3 calls also can be asynchronous but this is solely for learning purposes. Any other use case is also welcome, like blocking IO operations where there is no loop or suitable place to put the
isActive
check.
z

Zach Klippenstein (he/him) [MOD]

12/11/2020, 8:32 PM
In general io calls and synchronization primitives on the jvm use interrupts to cancel, and there’s a coroutine integration for that - I think it’s called
runInterruptable
or something
1
👍 1
a

Azur Haljeta

12/11/2020, 8:46 PM
this one seemed like a perfect solution unless it isn't it still can't interrupt the blocking call on time, i.e. when the timeout exceeds
z

Zach Klippenstein (he/him) [MOD]

12/12/2020, 1:10 AM
Can’t it? Looks like it should interrupt the thread if the job is cancelled: https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/src/Interruptible.kt#L143
so e.g.
withTimeout
should just work in something like
Copy code
withTimeout(1000) {
  someBlockingCall()
}
j

Joris PZ

12/12/2020, 10:24 AM
But doesn't that depend on whether or not
someBlockingCall
supports interruption? From the Java documentation:
Copy code
What if a thread goes a long time without invoking a method that throws InterruptedException? Then it must periodically invoke Thread.interrupted, which returns true if an interrupt has been received. For example:

for (int i = 0; i < inputs.length; i++) {
    heavyCrunch(inputs[i]);
    if (Thread.interrupted()) {
        // We've been interrupted: no more crunching.
        return;
    }
}

In this simple example, the code simply tests for the interrupt and exits the thread if one has been received. In more complex applications, it might make more sense to throw an InterruptedException:

if (Thread.interrupted()) {
    throw new InterruptedException();
}
So if
someBlockingCall
calculated Fibonacci numbers in an infinite loop without checking for
Thread.interrupted()
then I don't think there's anything you can do to interrupt it
a

Azur Haljeta

12/12/2020, 10:25 AM
@Zach Klippenstein (he/him) [MOD] yes, it should interrupt the thread but the thread itself should be interruptible which mostly isn't the case for legacy code or IO the are quite a few issues here: • The docs are misleading and cover too basic use cases • You still have to deal with threads directly here although nobody should do this when using coroutines. But since this has a big legacy background, coroutines can't just fix this magically either.
@Joris PZ you're right, but these examples with for loops and doing computation are too basic, and they are also taken too much seriously in the coroutines documentation. How about some real stuff like doing blocking IO? Nobody calculates fibonacci series in their apps 😄 The big difference is that you can't just easily check for interruption in blocking IO code. Seems that this java doc page is also crazy about computation code, and the coroutines docs also went into that direction.
seems that almost nobody is aware of
runInterruptible
https://stackoverflow.com/search?q=runInterruptible Only three questions at the time of writing
j

Joris PZ

12/12/2020, 10:39 AM
I think most people don't bother with interruption, and run their blocking IO on the IO dispatcher (the name isn't a coincidence). Or alternatively use non-blocking IO and asynchronous/suspending code to make sure no thread never blocks on IO.
a

Azur Haljeta

12/12/2020, 10:41 AM
The (non)blocking itself isn't an issue, since it's easy to implement, and yes – most devs don't bother with it that much. However, not being able to limit the execution time of this kind of code is the big deal (i.e.
withTimeout{ }
)
j

Joris PZ

12/12/2020, 10:43 AM
But then I don't understand the issue here - either your code is non-blocking and trivial to cancel, or it is blocking and does or does not support interruption, at the mercy of the author of the blocking code. I'm not sure what you're looking for further than that?
a

Azur Haljeta

12/12/2020, 10:46 AM
I wan't to make my code interruptible so that using either
withTimeout
or
runInterruptible
or both – will work, as I've explained in the first 2-3 messages in this thread. Also all of this is for learning and gaining more advanced skills with coroutines.
j

Joris PZ

12/12/2020, 10:53 AM
Then I'd say that if you're using non-blocking IO throughout and not doing crazy long computations without checking for
isActive
, then
withTimeout
should just work. Your code will run until the next suspension point and be interrupted there. As far as I know, all Kotlin library suspending functions like
delay
support cancellation. If you build your own suspension logic, using
suspendCancellableCoroutine
(docs) will make your code properly support cancellation. If you're calling into non-coroutine based code that blocks on either IO or computation, than you should run it on the IO dispatcher. Using
runInterruptible
will work if and only if the code supports it.
a

Azur Haljeta

12/12/2020, 11:00 AM
Using 
runInterruptible
 will work if and only if the code supports it.
That's the million dollar question here the whole time – how to make that code to support it. How to write it so that it plays nicely with cancellation. How to make it cooperative.
j

Joris PZ

12/12/2020, 11:18 AM
That's answered by the Java documentation I linked earlier, it's as simple as checking for
Thread.interrupted()
at points in your code, and returning or throwing an exception when its
true
a

Azur Haljeta

12/12/2020, 11:26 AM
The Java documentation you linked only deals with simple code where it's easy enough to put these checks, and I know how to do this and it isn't an issue. Now we're cycling back to the very first message of this thread and the original question: What if there are no suitable points for doing these checks? Where do I put this if I'm not dealing with some computation code or fibonacci series?
j

Joris PZ

12/12/2020, 11:40 AM
I don't know. I can't help you because I have no idea with kind of (non-IO) code could possibly be so computationally expensive that it needs to support cancellation, but doesn't use any kind of loop or recursion.
a

Azur Haljeta

12/12/2020, 11:42 AM
I'm even not doing computational code at all. That's not even close. I had a feeling the whole time that you didn't read my concerns from the beginning since I always had to point them back. Anyway, thanks for your help.
z

Zach Klippenstein (he/him) [MOD]

12/12/2020, 8:18 PM
You can also write all your IO operations to use their own short timeouts and loops, so you can periodically check the isActive status for long operations. Although it might just be simpler to use IO apis that support interruption.
1
4 Views