파라오 슬롯 SupervisorJob
코틀린 파라오 슬롯(12)
파라오 슬롯 SupervisorJob
SupervisorJob은 자식 파라오 슬롯에서 발생한 예외에 영향을 받지 않는다. 이 말을 이해하기 위해서 먼저 앞서 살펴봤던 예외 전파에 대해서 다시 살펴보자.
Job의 예외 전파
바카라 꽁 머니 예외 처리에서 예제 코드를 기반으로 구조화된 동시성으로 인해 발생하는 예외 전파에 대해 살펴봤다. 아래 예제 코드는 그때 봤던 코드에 catch 문 한 줄만 추가한 것이다.
실행 결과는 다음과 같다. catch 문을 추가한 이유는 실제로 부모로부터 취소 예외를 전달받은 것을 확인하기 위함이다.
Hello
Coroutine 1
Coroutine 1 was cancelled
Check: kotlinx.coroutines.JobCancellationException: Parent job is Cancelling; job="coroutine#2":StandaloneCoroutine{Cancelling}@3fd15b16
Coroutine 2 was cancelled
Caught in CoroutineExceptionHandler java.lang.NullPointerException
End
위 코드는 launch를 통해 파라오 슬롯을 생성했다. 이 경우는 파라오 슬롯을 생성할 때 Job 객체가 반환된다. 즉 일반적인 Job을 사용한 것이다. 다시 말하면 일반 Job을 사용하면 구조화된 동시성으로 인한 예외 전파가 발생한다고 할 수 있다. 이것을 도식화하면 다음과 같다.
일반 Job:
1) 자식 파라오 슬롯에서 예외가 발생하면 해당 예외가 부모 파라오 슬롯에게 전파됩니다.
2) 부모 파라오 슬롯은 예외를 감지하고 나머지 자식 파라오 슬롯들에게 예외를 전파하여 취소합니다.
3) 부모 파라오 슬롯도 취소됩니다.
자식 파라오 슬롯인 Coroutine 1에서 발생한 예외가 부모 파라오 슬롯에 전파된다. 그러면 부모 파라오 슬롯은 예외를 처리하고 자신도 종료(=취소)할 준비를 한다. 그 후 부모 파라오 슬롯은 다른 자식 파라오 슬롯인 Coroutine 2를 종료하기 위해 취소 예외를 전파한다. 그림에서 1번의 화살표 방향과 2번의 화살표 방향을 보면 된다. 이러한 관점으로 보면 예외 전파는 양방향 전파라고 할 수 있다.
즉 양방향 전파는 자식 파라오 슬롯이 실패하면 부모 파라오 슬롯도 취소되고, 부모 파라오 슬롯이 취소되면 자식 파라오 슬롯도 취소되는 경우를 의미한다. (단순히 양방향 전파라고 하면 마치 자식 파라오 슬롯에서 발생한 NullPointerException이 양방향으로 전파하는 것 같은 느낌을 준다. 그래서 양방향 전파라는 표현이 적절한지 의문이다.)
SupervisorJob의 예외 전파
SupervisorJob을 사용하면 자식 파라오 슬롯들은 독립적으로 예외를 처리한다. 즉, 하나의 자식 파라오 슬롯에서 발생한 예외가 다른 자식 파라오 슬롯으로 전파되지 않는다. 부모 파라오 슬롯인 SupervisorJob이 취소될 경우에만 모든 자식 파라오 슬롯이 취소된다.
실제로 그렇게 동작하는지 앞선 예제를 조금 수정하여 확인해 보자.
실행 결과는 다음과 같다.
Hello
Coroutine 1
Coroutine 1 was cancelled
Caught in CoroutineExceptionHandler java.lang.NullPointerException
Coroutine 2
Coroutine 2 has completed
Goodbye
End
결과를 보면 자식 파라오 슬롯인 Coroutine 1은 실행 후 예외가 발생하여 취소된 것을 알 수 있다. 예외가 전파되었다면 Coroutine 2는 실행되기 전에 취소가 될 것이다. 하지만 실제 결과는 Coroutine 2가 실행되었고 완료된 것까지 확인할 수 있다. 그리고 부모 파라오 슬롯도 취소되지 않아서 Goodbye라는 문구까지 출력되고 있다.
기존에 알고 있던 예외 전파의 규칙을 완전히 무시하고 있는 상황이다. 이렇게 하나의 자식 파라오 슬롯에서 발생한 예외로 모든 파라오 슬롯이 종료되지 않도록 할 수 있는 방법이 SupervisorJob을 사용하는 것이다.
이 상황을 도식화하면 다음과 같다.
SupervisorJob인 자식 파라오 슬롯에서 발생한 예외는 자식 파라오 슬롯 내에서 처리되고 다른 곳으로 전파되지 않는다. 따라서 부모 파라오 슬롯을 포함한 자식 파라오 슬롯은 종료되지 않는다. 예외가 다른 곳으로 전파되지 않고 자신에게 향하는 하나의 방향만 가지고 있기 때문에 단방향 전파라고 볼 수 있다. (하지만 단방향이란 표현 또한 적절한 표현인지는 개인적으로 의문이다. 굳이 그렇게 말할 필요가 있나 싶다.)
SupervisorJob:
1) 자식 파라오 슬롯에서 예외가 발생해도 해당 예외는 부모 파라오 슬롯이나 다른 자식 파라오 슬롯에게 전파되지 않는다.
2) 부모 파라오 슬롯과 다른 자식 파라오 슬롯은 계속 실행된다.
이러한 동작을 통해 SupervisorJob은 자식 파라오 슬롯의 실패가 전체 파라오 슬롯 구조에 영향을 미치지 않도록 한다. 그래서 SupervisorJob은 독립적으로 예외를 처리한다고 표현하는 것이 좀 더 명확하지 않나 싶다.
supervisorScope
앞선 예제에서는 SupervisorJob을 사용하여 파라오 슬롯 스코프(CoroutineScope)를 생성하였다. 이것보다 좀 더 편리하게 자식 파라오 슬롯을 모두 SupervisorJob으로 만들 수 있는 방법이 있다. 바로 supervisorScope를 사용하는 것이다. supervisorScope는 coroutineScope처럼 새로운 파라오 슬롯 스코프를 생성하는 스코핑 함수이다. 다만supervisorScope 내에서 실행되는 자식 파라오 슬롯들은 SupervisorJob의 특성을 가진다.따라서 자식 파라오 슬롯이 독립적으로 예외를 처리할 수 있다. supervisorScope는 자식 파라오 슬롯들을 포함하는 부모 파라오 슬롯의 역할을 한다.
앞선 예제를 다음과 같이 조금 수정해 보자.
실행 결과는 다음과 같다. CoroutineScope(SupervisorJob())을 사용했을 때와 결과가 똑같다.
Hello
Coroutine 1
Coroutine 1 was cancelled
Caught in CoroutineExceptionHandler java.lang.NullPointerException
Coroutine 2
Coroutine 2 has completed
Goodbye
End
GlobalScope.launch 대신 supervisorScope를 사용하였다. supervisorScope는 내부적으로 SupervisorJob을 포함하는 새로운 CoroutineScope를 생성한다. 그렇기 때문에 결국 기존에 사용했던 CoroutineScope(SupervisorJob())와 같은 의미다. 두 경우 모두 SupervisorJob을 사용하여 자식 파라오 슬롯이 독립적으로 예외를 처리한다.
자식 파라오 슬롯이 독립적으로 예외를 처리되는 것 역시 같기 때문에 예외 전파의 모습도 동일하다.
바카라 꽁 머니 예외 처리에서 CoroutineExceptionHandler가 자식 파라오 슬롯에서 사용되지 않는다고 했었다. 예외 전파를 통해 부모에서 처리하기 때문이다. 하지만supervisorScope는 SupervisorJob을 사용하여 자식 파라오 슬롯이 독립적으로 예외 처리를 한다고 했다. 그러므로 자식 파라오 슬롯에 CoroutineExceptionHandler를 사용하면 자식 파라오 슬롯이 실패할 때 CoroutineExceptionHandler가 해당 예외를 처리할 수 있다. 따라서supervisorScope 내에서는 자식 파라오 슬롯에 사용해도 의미가 있다.
요약하자.
- SupervisorJob은 자식 파라오 슬롯에서 발생한 예외에 영향을 받지 않는다.
- 양방향 예외 전파는 자식 파라오 슬롯이 실패하면 부모 파라오 슬롯도 취소되고, 부모 파라오 슬롯이 취소되면 자식 파라오 슬롯도 취소되는 경우를 의미한다.
- supervisorScope는 coroutineScope처럼 새로운 파라오 슬롯 스코프를 생성하는 스코핑 함수이다.
- supervisorScope 내에서 실행되는 자식 파라오 슬롯들은 SupervisorJob의 특성을 가진다.