03
29

https://kotlinlang.org/docs/exception-handling.html

 

Coroutine exceptions handling | Kotlin

 

kotlinlang.org

์œ„ ๋งํฌ๋ฅผ ๊ณต๋ถ€ํ•˜๋ฉฐ ๊ธฐ๋กํ•œ ๊ธ€์ด๋‹ค

 

CoroutineExceptionHandler

์ต์…‰์…˜ ํ•ธ๋“ค๋Ÿฌ๋Š” launch์™€ ๊ฐ™์€ ์ฝ”๋ฃจํ‹ด๋นŒ๋”์—์„œ ๋ฐœ์ƒํ•˜๋Š” uncaught ์˜ˆ์™ธ์— ๋Œ€์‘ํ• ์ˆ˜์žˆ๋‹ค. async์™€ ๊ฐ™์ด ๋ชจ๋“  ์˜ˆ์™ธ๋ฅผ ์‚ฌ์šฉ์ž ๋‹จ์—์„œ ์ฒ˜๋ฆฌํ• ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ์—๋Š” ์ด ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ํšจ๊ณผ๊ฐ€ ์—†๋‹ค.

@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception -> 
        println("CoroutineExceptionHandler got $exception") 
    }
    val job = GlobalScope.launch(handler) { // root coroutine, running in GlobalScope
        throw AssertionError()
    }
    val deferred = GlobalScope.async(handler) { // also root, but async instead of launch
        throw ArithmeticException() // Nothing will be printed, relying on user to call deferred.await()
    }
    joinAll(job, deferred)    
}

Globalscope์—์„œ root ์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„๋กœ ์“ฐ๊ธฐ์œ„ํ•ด @OptIn ์–ด๋…ธํ…Œ์ด์…˜์ด ํ•„์š”ํ•˜๋‹ค. ์ด๋Ÿฌ๋ฉด job์—์„œ ๋ฐœ์ƒํ•œ uncaught ์˜ˆ์™ธ๋ฅผ ํ•ธ๋“ค๋Ÿฌ์—์„œ ์ฒ˜๋ฆฌํ•ด์ค€๋‹ค.

 

Cancellation and Exceptions

์ทจ์†Œ๋Š” ์˜ˆ์™ธ์™€ ์œ ์‚ฌํ•˜๋‹ค๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ทจ์†Œ๋ฅผ ํ• ๋•Œ ์ฝ”๋ฃจํ‹ด์€ CancellationException์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์ธ๋ฐ, ์ด ์˜ˆ์™ธ๋Š” ๋”ฐ๋กœ ํ•ธ๋“ค๋Ÿฌ์—์„œ ์ฒ˜๋ฆฌ๋˜์ง€์•Š๊ณ  ๊ทธ๋ƒฅ ๋””๋ฒ„๊ทธ ์ •๋ณด๋กœ ํ™œ์šฉ๋œ๋‹ค. ์ „ํŒŒ๋˜์ง€์•Š๋Š” ์˜ˆ์™ธ์ฒ˜๋ฆฌ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ฃจํŠธ ์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„๊นŒ์ง€ ์ ์šฉ๋˜์ง€์•Š๊ณ  cancel์ด ๋ถ™์–ด์žˆ๋Š” ์Šค์ฝ”ํ”„๋งŒ ์ทจ์†Œ์‹œํ‚จ๋‹ค.

 

ํ•˜๋‚˜ ๋ณผ๋งŒํ•œ ์ฝ”๋“œ๊ฐ€ ์žˆ๋‹ค.

val handler = CoroutineExceptionHandler { _, exception -> 
    println("CoroutineExceptionHandler got $exception") 
}
val job = GlobalScope.launch(handler) {
    launch { // the first child
        try {
            delay(Long.MAX_VALUE)
        } finally {
            withContext(NonCancellable) {
                println("Children are cancelled, but exception is not handled until all children terminate")
                delay(100)
                println("The first child finished its non cancellable block")
            }
        }
    }
    launch { // the second child
        delay(10)
        println("Second child throws an exception")
        throw ArithmeticException()
    }
}
job.join()

Long.MAX_VALUE๋ฉด ์ผ๋ฐ˜์ ์ธ ์ƒํ™ฉ์—์„œ ๊ต‰์žฅํžˆ ๊ธด ์‹œ๊ฐ„์ธ๋ฐ uncaught ์˜ˆ์™ธ๋ฅผ ๋˜์ ธ์„œ ๊ฐ•์ œ๋กœ ์Šค์ฝ”ํ”„๋ฅผ ํƒˆ์ถœ์‹œํ‚จ๋‹ค.

์ด๋•Œ withContext๋Š” ๋”ฐ๋กœ ์›€์ง์—ฌ์„œ ์ด ์Šค์ฝ”ํ”„๊ฐ€ ๋๋‚˜์•ผ ํ•ธ๋“ค๋Ÿฌ์— ์žˆ๋Š” ๋ฉ”์‹œ์ง€๊ฐ€ ์ถœ๋ ฅ๋œ๋‹ค.

 

Exceptions aggregation

์˜ˆ์™ธ์ฒ˜๋ฆฌ ๊ธฐ๋ณธ์€ ์ œ์ผ ์ฒ˜์Œ์— ๋ฐœ์ƒํ•œ ๊ฒƒ์„ ๋จผ์ € ์ฒ˜๋ฆฌํ•œ๋‹ค๋Š” ๊ฐœ๋…์ด๋‹ค.

val handler = CoroutineExceptionHandler { _, exception ->
    println("CoroutineExceptionHandler got $exception")
}
val job = GlobalScope.launch(handler) {
    val inner = launch { // all this stack of coroutines will get cancelled
        launch {
            launch {
                throw IOException() // the original exception
            }
        }
    }
    try {
        inner.join()
    } catch (e: CancellationException) {
        println("Rethrowing CancellationException with original cause")
        throw e // cancellation exception is rethrown, yet the original IOException gets to the handler  
    }
}
job.join()

join์—์„œ ๋ฐœ์ƒํ•˜๋Š” IOException์„ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ๋จผ์ € ๊ฐ€์ ธ๊ฐ„๋‹ค.

 

Supervision

SupervisorJob์„ ์‚ฌ์šฉํ•ด ์‹คํŒจํ•œ ์ž‘์—…๋งŒ ๋‹ค์‹œ ์‹œ์ž‘ํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

fun main() = runBlocking {
    val supervisor = SupervisorJob()
    with(CoroutineScope(coroutineContext + supervisor)) {
        // launch the first child -- its exception is ignored for this example (don't do this in practice!)
        val firstChild = launch(CoroutineExceptionHandler { _, _ ->  }) {
            println("The first child is failing")
            throw AssertionError("The first child is cancelled")
        }
        // launch the second child
        val secondChild = launch {
            firstChild.join()
            // Cancellation of the first child is not propagated to the second child
            println("The first child is cancelled: ${firstChild.isCancelled}, but the second one is still active")
            try {
                delay(Long.MAX_VALUE)
            } finally {
                // But cancellation of the supervisor is propagated
                println("The second child is cancelled because the supervisor was cancelled")
            }
        }
        // wait until the first child fails & completes
        firstChild.join()
        println("Cancelling the supervisor")
        supervisor.cancel()
        secondChild.join()
    }
}

์Šˆํผ๋ฐ”์ด์ €๋ฅผ ์บ”์Šฌํ–ˆ๋Š”๋ฐ secondChild๋กœ ์ „ํŒŒ๋˜๋Š” ๋ชจ์Šต์ด ๋ณด์ธ๋‹ค.

fun main() = runBlocking {
    try {
        supervisorScope {
            val child = launch {
                try {
                    println("The child is sleeping")
                    delay(Long.MAX_VALUE)
                } finally {
                    println("The child is cancelled")
                }
            }
            // Give our child a chance to execute and print using yield 
            yield()
            println("Throwing an exception from the scope")
            throw AssertionError()
        }
    } catch(e: AssertionError) {
        println("Caught an assertion error")
    }
}

supervisorScope๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋Š”๋ฐ, uncaught์˜ˆ์™ธ๊ฐ€ ๋ฃจํŠธ๊นŒ์ง€ ๋ฐ”๋กœ ์•ˆํผ์ง€๊ฒŒ ํ•œ๋‹ค. ์›๋ž˜ ์˜ˆ์™ธ๊ฐ€ ์–‘๋ฐฉํ–ฅ์ด์—ˆ๋‹ค๋ฉด ์Šˆํผ๋ฐ”์ด์ € ์Šค์ฝ”ํ”„์—์„œ๋Š” ๋‹จ๋ฐฉํ–ฅ์œผ๋กœ ์ ์šฉ๋ผ์„œ AssertionError๊ฐ€ ๋ฐ”๋กœ runBlocking์œผ๋กœ ๊ฐ€์ง€์•Š๊ณ  supervisorScope์—์„œ ๋จผ์ € ์ฒ˜๋ฆฌ๋˜๊ณ , ๋ถ€๋ชจ์Šค์ฝ”ํ”„๋กœ ๊ฐ„๋‹ค.

 

๋”ฐ๋ผ์„œ ์Šˆํผ๋ฐ”์ด์ € ์Šค์ฝ”ํ”„์—์„œ ์ง์ ‘ ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋ ค๋ฉด handler๋ฅผ ์ง์ ‘ ๋„ฃ์–ด์„œ ์ฒ˜๋ฆฌํ•˜๋ฉด ๋œ๋‹ค. ์ด๋Ÿฌ๋ฉด ๋ฃจํŠธ์Šค์ฝ”ํ”„์ฒ˜๋Ÿผ ์ž‘๋™ํ•œ๋‹ค.

fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception -> 
        println("CoroutineExceptionHandler got $exception") 
    }
    supervisorScope {
        val child = launch(handler) {
            println("The child throws an exception")
            throw AssertionError()
        }
        println("The scope is completing")
    }
    println("The scope is completed")
}
"๋Œ“๊ธ€, ๊ณต๊ฐ ๋ฒ„ํŠผ ํ•œ ๋ฒˆ์”ฉ ๋ˆ„๋ฅด๊ณ  ๊ฐ€์ฃผ์‹œ๋ฉด ํฐ ํž˜์ด ๋ฉ๋‹ˆ๋‹ค"
๋ฐ˜์‘ํ˜•
COMMENT