kotlinintermediate

Coroutine Timeouts and Cancellation

Control coroutine lifecycle: withTimeout, withTimeoutOrNull, isActive checking, and cooperative cancellation.

kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    // withTimeout — throws TimeoutCancellationException
    println("--- withTimeout ---")
    try {
        withTimeout(200) {
            repeat(10) { i ->
                println("Working $i...")
                delay(100)
            }
        }
    } catch (e: TimeoutCancellationException) {
        println("Timed out: ${e.message}")
    }

    // withTimeoutOrNull — returns null on timeout
    println("\n--- withTimeoutOrNull ---")
    val result = withTimeoutOrNull(200) {
        delay(500)
        "completed"
    }
    println("Result: $result") // null

    val fast = withTimeoutOrNull(200) {
        delay(50)
        "fast result"
    }
    println("Fast: $fast") // fast result

    // Cooperative cancellation — check isActive
    println("\n--- Cooperative ---")
    val cpuJob = launch(Dispatchers.Default) {
        var count = 0L
        while (isActive) { // cooperative check
            count++
            if (count % 1_000_000 == 0L) {
                println("Count: $count")
                yield() // or ensureActive()
            }
        }
        println("CPU work cancelled at $count")
    }
    delay(50)
    cpuJob.cancelAndJoin()

    // Cancellation with cleanup
    println("\n--- Cleanup ---")
    val resource = launch {
        try {
            println("Acquired resource")
            delay(Long.MAX_VALUE) // suspended
        } catch (e: CancellationException) {
            println("Cancellation received")
            throw e
        } finally {
            withContext(NonCancellable) {
                println("Releasing resource...")
                delay(100) // cleanup allowed
                println("Resource released")
            }
        }
    }
    delay(50)
    resource.cancelAndJoin()

    // Race — first result wins
    println("\n--- Race ---")
    suspend fun fetchFromServer(name: String, delayMs: Long): String {
        delay(delayMs)
        return "$name responded"
    }

    val winner = select<String> {
        async { fetchFromServer("Server-A", 300) }.onAwait { it }
        async { fetchFromServer("Server-B", 100) }.onAwait { it }
        async { fetchFromServer("Server-C", 200) }.onAwait { it }
    }
    println("Winner: $winner")

    // Timeout with fallback
    println("\n--- Timeout fallback ---")
    suspend fun fetchWithFallback(): String {
        return withTimeoutOrNull(100) {
            delay(200) // too slow
            "primary data"
        } ?: run {
            println("Primary timed out, using fallback")
            "fallback data"
        }
    }
    println("Data: ${fetchWithFallback()}")

    // Deadlined scope
    println("\n--- Deadline ---")
    val deadline = System.currentTimeMillis() + 300
    coroutineScope {
        val tasks = (1..5).map { i ->
            async {
                val remaining = deadline - System.currentTimeMillis()
                withTimeoutOrNull(remaining.coerceAtLeast(0)) {
                    delay(i * 100L)
                    "Task $i done"
                } ?: "Task $i: timed out"
            }
        }
        tasks.map { it.await() }.forEach { println(it) }
    }
}

Use Cases

  • API call timeout handling
  • Cooperative cancellation in CPU-bound work
  • Race condition patterns for redundancy

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.