kotlinadvanced

SupervisorJob and Error Isolation

Isolate coroutine failures with SupervisorJob: independent child coroutines, partial failure handling.

kotlin
import kotlinx.coroutines.*

// Without supervisor — one failure cancels siblings
suspend fun withoutSupervisor() {
    println("--- Without Supervisor ---")
    try {
        coroutineScope {
            launch {
                delay(100)
                println("  Child 1 started")
                delay(500)
                println("  Child 1 completed") // Won't reach
            }
            launch {
                delay(200)
                println("  Child 2 about to fail")
                throw RuntimeException("Child 2 failed!")
            }
            launch {
                delay(100)
                println("  Child 3 started")
                delay(500)
                println("  Child 3 completed") // Won't reach
            }
        }
    } catch (e: RuntimeException) {
        println("  Caught: ${e.message}")
        println("  All children were cancelled!")
    }
}

// With supervisor — failures are isolated
suspend fun withSupervisor() {
    println("\n--- With Supervisor ---")
    supervisorScope {
        val child1 = launch {
            delay(100)
            println("  Child 1 started")
            delay(500)
            println("  Child 1 completed ✓")
        }
        val child2 = launch {
            delay(200)
            println("  Child 2 about to fail")
            throw RuntimeException("Child 2 failed!")
        }
        val child3 = launch {
            delay(100)
            println("  Child 3 started")
            delay(500)
            println("  Child 3 completed ✓")
        }

        // Handle child2's failure
        child2.invokeOnCompletion { cause ->
            if (cause != null) println("  Child 2 failed: ${cause.message}")
        }

        // Wait for all
        listOf(child1, child3).forEach { it.join() }
    }
}

// Practical: parallel data fetching with partial results
data class FetchResult<T>(
    val source: String,
    val data: T? = null,
    val error: String? = null
) {
    val isSuccess get() = data != null
}

suspend fun <T> safeFetch(
    source: String,
    block: suspend () -> T
): FetchResult<T> = try {
    FetchResult(source, data = block())
} catch (e: Exception) {
    FetchResult(source, error = e.message)
}

suspend fun fetchAllData() = supervisorScope {
    println("\n--- Partial Fetch ---")

    val results = listOf(
        async { safeFetch("users") { delay(100); listOf("Alice", "Bob") } },
        async { safeFetch("orders") { delay(50); throw RuntimeException("DB timeout") } },
        async { safeFetch("products") { delay(150); listOf("Laptop", "Mouse") } },
        async { safeFetch("metrics") { delay(200); mapOf("views" to 1000) } }
    ).awaitAll()

    val (successes, failures) = results.partition { it.isSuccess }
    println("  Successes: ${successes.map { "${it.source}: ${it.data}" }}")
    println("  Failures: ${failures.map { "${it.source}: ${it.error}" }}")

    results
}

// SupervisorJob in a CoroutineScope
class TaskRunner {
    private val scope = CoroutineScope(
        SupervisorJob() + Dispatchers.Default + CoroutineExceptionHandler { _, e ->
            println("  [Handler] Uncaught: ${e.message}")
        }
    )

    fun runTask(name: String, block: suspend () -> Unit): Job =
        scope.launch {
            println("  Task '$name' starting")
            block()
            println("  Task '$name' completed")
        }

    fun shutdown() {
        scope.cancel()
        println("  TaskRunner shut down")
    }
}

fun main() = runBlocking {
    withoutSupervisor()
    withSupervisor()
    fetchAllData()

    // TaskRunner with SupervisorJob
    println("\n--- TaskRunner ---")
    val runner = TaskRunner()
    val t1 = runner.runTask("A") { delay(200) }
    val t2 = runner.runTask("B") { delay(100); throw RuntimeException("B failed") }
    val t3 = runner.runTask("C") { delay(300) }

    delay(500)
    runner.shutdown()
}

Use Cases

  • Independent failure isolation in microservices
  • Partial result aggregation from multiple sources
  • Resilient background task management

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.