Kotlin coroutine withTimeout

I’m trying to grasp the coroutinescopes more and I’m having difficulties understanding some of the issues I’m encountering. An example of such is the withTimeout coroutine. I have the following example:

Link: Kotlin Playground: Edit, Run, Share Kotlin Code Online

import kotlin.system.exitProcess
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout

// Expect a hard kill as the inner coroutine is taking much longer than the outer
suspend fun main() {
    println("start")
    coroutineScope {
        withContext(Dispatchers.Default) {
            withTimeout(5_000) {
                val startTime = System.currentTimeMillis()
                println("with 5sec timeout")
                delay(3_000)
                try {
                    withTimeout(10_000) {
                        println("with 3sec timeout")
                        delay(3_000)
                        println("passed 5sec timeout")
                        delay(3_000)
                    }
                } catch (t: Throwable) {
                    println("caught ${t.message} after ${System.currentTimeMillis() - startTime}")
                    throw t
                } finally {
                    println("Did finally after ${System.currentTimeMillis() - startTime}")
                }
            }
        }
    }
    println("done")
    exitProcess(0)
}

As shown we have a suspendable function and a coroutineScope is created. This has a withContext, currently with the Default dispatcher, I’ve also tried it without this dispatcher. Subsequently there’s a nested withTimeout.

What I would be expecting is that the outer timeout would throw a TimeoutCancellationException as the inner timeout is surpassing the threshold of the 5s of the outer. What you can however see happening is that the catch is catching the exception.

The questions/difficulties that I have:

  1. Is the nested timeout joining context causing the catch to trigger?
  2. What would be a solution where the nested timeout structure would work out as expected
  3. Any other suggestions are welcome

Of course the example here is a bit daft, but a scenario with a larger outer and lower inner timeouts would be more suitable. I find the behaviour strange and trying to understand what’s happening and I’m missing.

I think your understanding is generally correct, and the code does exactly what you said. It throws due to exceeding the outer timeout: kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 5000 ms - Kotlin Playground: Edit, Run, Share Kotlin Code Online

Did you expect it won’t be caught by the catch block? Why is that? This is where he coroutine is right now, so this is where it throws, but it was triggered by the outer timeout. Remember cancallations are cooperative, we can’t just “kill” a coroutine. We cancel inner coroutines by throwing from them.

Right, that helps make it a tiny bit more clear. I was under the impression that the withTimeout would add a child coroutine to the/a job.

My assumption was that if it is in fact a parent/child relation that the parent would fail and therefore that would cancel the child without it triggering the try/catch of said block. What you said however makes sense too, from the documentation I understand that the withTimeout does not create a new coroutine but does it within the existing scope. I get the concept of it now, just have to imprint it in my brain a bit more.

Thanks for clearing it up.

Well, I think the main point here is not really if we have two coroutines: one outer and one inner or only a single one. This is also not entirely clear to me and I feel sometimes we say there are two and sometimes there is a single one. I think it depends on the perspective and both answers are somehow true.

However, the main point is that cancellations in coroutines are handled by throwing an exception. It doesn’t matter if we the coroutine failed or it was cancelled - in both cases it will throw. Please note the exception was an CancellationException, which is a very special type of exception, used to signal cancellations.

1 Like