kotlin Coroutine: 코루틴 예외 전파 제한 왜 하는거지?(with SupervisorJob)
스터디원중 한 분께서 코루틴 예외 전파에 대한 블로그를 공유해주셨다. 이 글을 보고 직접 코루틴 예외 전파에 대한 실습을 진행해보고자 한다.
코루틴을 공부하면서 가장 많이 듣는 단어는 '구조화된 동시성'이다. 간단하게 설명하자면, 모든 자식 코루틴은 부모 코루틴의 CoroutineContext을 공유 공유하기 때문에 취소, 예외, 완료 처리 또한 계층적으로 전파된다는 뜻이다.
이러한 특징은 부모-자식 간 생명주기를 통일해서 불필요한 리소스 낭비나 누수를 방지해줄 수 있다는 점에서 큰 장점이기도 하다.
만약 여러 개의 비동기 작업을 동시에 실행한다고 할 때, 특정 작업에서 예외가 발생했다고 해서 다른 모든 작업이 중단되어야 할까? 분명 그렇지 않는 경우도 있을 것이다. (한 화면에서 여러 독립적인 데이터를 로드하는 경우가 특히 그렇다)
하나의 데이터 로드에 실패하더라도 나머지 데이터는 화면에 보여줌으로써 사용자 경험을 해치지 않아야 하는 경우, 혹은 일부 작업의 실패가 전체 시스템의 중단으로 이어지는 것을 막아야 하는 경우에서는 예외 전파가 필요할 수 있다.
따라서 평소에 많이 접해보지 못한 부분이라 해당 상황을 직접 만들어보고 테스트해보기로 했다.
먼저 영화 정보를 불러오는 간단한 프로젝트를 만들었고, 서로 다른 정보를 제공하는 2개의 API를 사용했다.(영화진흥위원회 API의 경우 포스터 이미지를 제공하지 않기 때문에 포스터 이미지 url을 제공하는 Omdb Open API에서 따로 받아야 한다)
문제는 Omdb api 사용에 포스터 정보를 얻으려면 영문 제목을 쿼리로 넘겨줘야하는데, kobis의 boxOffice 리스트에는 영문 제목이 포함되어있지 않다는 것이다.🥲 (API 선정을 단단히 잘못함)
결국 영문 제목을 얻으려면 boxOffice로 얻은 영화 정보 중 movieCd값을 가지고 영화 상세정보 api를 추가로 호출하는 방법밖엔 없다.
즉 화면에 있는 모든 정보를 보여주려면 boxoffice -> movieInfo -> moviePoster api가 순차적으로 성공해야하는데, 이번 예외전파를 테스트하기에는 적합하지 않아 의도적으로 환경을 구성해서 테스트할 예정이다.
실습에서는 '영화 정보 목록'과 '영화 포스터' 두 가지 정보를 비동기적으로 가져오는 시나리오에서 코루틴의 예외 전파를 SupervisorJob을 통해 아래의 순서로 제한해보려고 한다.
supervisorScpoe 내부에 각각 async로 실행시킨다.fun getMainData(date: String, code: String) {
viewModelScope.launch {
supervisorScope {
// 영화 박스오피스 정보
val infoDeferred = async {
val result = mainRepository.getDailBoxOfficeList(KOBIS_API_KEY, date,
result.boxOfficeResult!!.dailyBoxOfficeList
}
// 영화 포스터 정보
val imageDeferred = async {
val posters = mutableListOf<String>()
movieNameEnList.forEach { title ->
val result = mainRepository.getMoviePoster(OMDB_API_KEY, title)
posters.add(result.Poster ?: "")
}
posters
}
val dailyBoxOfficeListResult = try {
infoDeferred.await()
} catch (e: Exception) {
Log.e("MainViewModel", "영화 박스오피스 정보 로드 실패: ${e.message}", e)
emptyList()
}
val moviePosterListResult = try {
imageDeferred.await()
} catch (e: Exception) {
Log.e("MainViewModel", "영화 포스터 로드 실패: ${e.message}", e)
emptyList()
}
_dailyMovieBoxList.value = dailyBoxOfficeListResult
_moviePosterList.value = moviePosterListResult
}
}
}
위 함수를 호출했을 때 메인화면에서 두 정보를 잘 가져오는 것을 볼 수 있다.

다음으로 포스터 정보를 가져오는 Omdb API KEY를 수정하여 의도적으로 에러를 발생시켜보았다. 아래 에러 로그를 보면 401에러로 인해 포스터 정보 요청이 모두 실패한 것을 확인할 수 있다.

하지만 포스터 API 요청이 실패했음에도, 해당 예외가 다른 작업으로 전파되지 않았기 때문에,

의도한 대로 포스터는 "No Image"로 대체되고, 영화 제목은 정상적으로 화면에 노출되는 것을 확인할 수 있었다.
물론 이 예제는 supervisorScope를 활용한 예외 전파 제어를 실습해보기 위해 일부러 구성한 시나리오라 실제 서비스 코드에서의 최선의 방식이라고 얘기 할 수는 없다.
그래도 직접 동작을 눈으로 보고 체감해봤다는 점에서 의미 있는 실습이었다고 생각한다.