태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.
2019.03.04 21:48


[coroutine] Exception handling


https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/exception-handling.md#exception-handling


actor, android uncaughtexceptionprehandler, Async, cancellation and exceptions, cancellationexception, children terminate, context element, coroutine exception, coroutine exception handling, coroutineexceptionhandler, exception aggregation, Exception Handling, Exception Propagation, exception.suppressed, global coroutine exception handler, globalscope, globalscope default exceptionhandler, jdk version, launch, main coroutine, parent propagation, produce, serviceloader, uncaughtexceptionhandler, unhandled exception


Exception propagation

-

Coroutine builder 는 2가지 flavor 가 있다.

exception 을 자동으로 전파하는 launch 와 actor류, 그리고 user 에게 노출되는(돼왕: api call 을 통해 얻어갈 수 있는) async 와 receive 류.

자동 전파하는 케이스는 Java 의 uncaughtExceptionHandler 와 비슷하게 unhandled exception 이다. (돼왕: 바로 crash 전단계로 간다?)

반면에 user 에게 노출되는 경우는 user 에게 exception 을 handle 하도록 한다. (돼왕: try-catch 할 수 있다.)



-

runBlocking{
    val job = GlobalScope.launch{
        println(“Throwing exception from launch”)
        throw IndexOutOfBoundsException()
    }
    job.join()
    println(“Joined failed job”)

    val deferred = GlobalScope.async{
        println(“Throwing exception from async”)
        throw ArithmeticException()
    }

    try{
        deferred.await()
        println(“Unreached”)
    } catch(e:ArithmeticException){
        println(“Caught ArithmeticException”)
    }
}

결과는..

Throwing exception from launch

Exception in thread "DefaultDispatcher-worker-2 @coroutine#2" java.lang.IndexOutOfBoundsException // 돼왕 : GlobalScope 의 기본 exceptionHandler 가 print

Joined failed job

Throwing exception from async // 돼왕 : await 가 불리기 전에 이미 throw ArithmeticException 코드는 탄 상태이다. await 해야 실제 바깥으로 throw 된다.

Caught ArithmeticException





CoroutineExceptionHandler

-

GlobalScope 가 모든 exception 을 print 하는 것을 원치 않는다면,

CoroutineExceptionHandler 라는 context element 를 사용하면 된다.

이 녀석은 Thread.uncaughtExceptionHandler 와 비슷하다.



-

JVM 에서 모든 coroutine 에 대한 global exception handler 를 재정의 할 수 있다.

이는 ServiceLoader 에 CoroutineExceptionHandler 를 등록하면 할 수 있다.

Global exception handler 는 Thread.defaultUncaughtExceptionHandler 와 비슷하다.

Android 에서는 uncaughtExceptionPreHandler 가 global coroutine exception handler 로 등록되어 있고, 이 녀석은 더 선행하여 불리는 exceptionHandler 가 없으면 불리게 된다.

CoroutineExceptionHandler 는 user 가 handle 하지 않는 exception 들에 대해서만 작동한다.

그래서 아래와 같이 async builder 를 사용하면 영향력이 없다.


runBlocking{
    val handler = CoroutineExceptionHandler { _, exception ->
        println(“Caught $exception”)
    }

    val job = GlobalScope.launch(handler){
        throw AssertionError()
    }

    val deferred = GlobalScope.async(handler){
        throw ArithmeticException()
    }
    joinAll(job, deferred)
}

결과는..

Caught java.lang.AssertionError // 돼왕 : async 는 await 로 값을 받아오기 전까지 exception 을 전달하지 않는다.





Cancellation and exceptions

-

Coroutine 은 내부적으로 취소시 CancellationException 을 사용한다.

이 exception 들은 모든 handler 에 의해 무시된다.(돼왕 : crash 를 내지 않는다.) 그래서 이들은 debug info 로만 사용되며, catch 로 잡을 수도 있긴 하다. 

(돼왕 : timeout 의 경우는 또 다른 관점으로 봐야 한다.)

coroutine 은 Job.cancel 를 통해 cause 없이 취소하면, parent 를 cancel 시키지 않고 job 에 종속된 그 녀석만 종료한다.    



-

coroutine 이 CancellationException 이외의 exception 을 마딱뜨리면, parent 를 exception 과 함께 종료시킨다.

이 동작은 override 되어 변경될 수 없고, CoroutineExceptionHandler 구현에 의존하지 않는 안정적인 구조화된 concurrency hierarchy 를 제공한다.

exception 은 모든 children 이 terminate 된 후에 parent 에 의해 처리된다.



-

runBlocking{
    val handler = CoroutineExceptionHandler{ _, exception ->
        println(“Caught $exception”)
    }

    val job = GlobalScope.launch(handler){
        launch{
            try{
                delay(Long.MAX_VALUE)
            } finally{
                withContext(NonCancellable){
                    println(“Children are cancelled, but exception is no handled until all children terminated”)
                    delay(100L)
                    println(“The first child finished its non cancellable block”)
                }
            }
        }

        launch{
            delay(10L)
            println(“Second child throws an exception”)
            throw ArithmeticException()
        }
    }
    job.join()
}

결과는..

Second child throws an exception

Children are cancelled, but exception is not handled until all children terminate

The first child finished its non cancellable block

Caught ArithmeticException


-

CoroutineExceptionHandler 는 GlobalScope 안에서 생성된 녀석에 항상 install 되어 있다.

main runBlocking scope 에서 launch 되는 coroutine 에 exception handler 를 설치하는 것은 큰 의미가 없다.

왜냐면 main coroutine 은 installed handler 에 상관없이 child 가 exception 으로 종료될 때 항상 cancel 되기 때문이다.






Exceptions aggregation

-

여러개의 children coroutine 에서 exception 을 던지면 어떻게 될까?

일반적인 규칙은 "처음 던져진 exception 이 이긴다” 이다.

그러나 이 경우 exception 이 유실될 수 있다.



-

runBlocking{
    val handler = CoroutineExceptionHandler { _, exception ->
        println(“Caught $exception with suppressed ${exception.suppressed.contentToString()}”)
    }

    val job = GloablScope.launch(handler){
        launch{
            try{
                delay(Long.MAX_VALUE)
            } finally{
                throw ArithmeticException()
            }
        }

        launch{
            delay(100L)
            throw IOException()
        }

        delay(Long.MAX_VALUE)
    }
    job.join()
}

결과는..

Caught java.io.IOException with suppressed [java.lang.ArithmeticException]

suppressed 는 JDK7 이상에서 작동한다.



-

Cancellation exception 은 기본적으로 투명하고 wrap 되지 않는다.

runBlocking{
    val handler = CoroutineExceptionHandler { _, exception ->
        println(“Caught original $exception”)
    }

    val job = GlobalScope.launch(handler){
        val inner = launch{
            launch{
                launch{
                    throw IOException()
                }
            }
        }

        try{
            inner.join()
        } catch(e:CancellationException){
            println(“Rethrowing CancellationException with original cause”)
            throw e
        }
    }
    job.join()
}

결과는..

Rethrowing CancellationException with original cause

Caught original java.io.IOException



-




댓글을 달아 주세요


Posted by 돼지왕왕돼지