1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Kotlin Coroutine] Coroutine内では runCatchingを使わないようにする

Posted at

はじめに

Kotlinで例外処理を書く際、runCatching が非常に便利です。
しかしCoroutine内で使用する場合、CancellationExceptionまで捕捉してしまい、キャンセルが上位に伝播しなくなるという問題があります。

本記事ではこの問題を避けるための対応を紹介します。

runCatchingの問題点

CoroutineのキャンセルはCancellationExceptionによって表現されます。
これはエラーというより制御フローの一部であり、親子のCoroutine間で正しく伝播することが重要です。

しかし、以下のようにrunCatchingを使うと問題が発生します。

suspend fun fetch(): Result<String> =
  runCatching {
    someFunction()
    "ok"
  }

この処理がキャンセルされた場合、内部で投げられたCancellationExceptionResult.failureに変換されてしまい、呼び出し元にキャンセルが伝わりません。
結果として、処理が継続したり、タイムアウトが正しく機能しないといった挙動につながります。

方針

対応方針は以下の通りです。

  • 通常の例外はResultに包んで返す
  • CancellationExceptionは捕捉せず、そのまま上位に再スローする

runSuspendCatchingの実装

上記方針を満たすため、以下のヘルパー関数を作成しました。
(GitHubのIssueに記載の方法です)

import kotlinx.coroutines.CancellationException

suspend inline fun <T> runSuspendCatching(
  crossinline block: suspend () -> T
): Result<T> =
  try {
    Result.success(block())
  } catch (e: CancellationException) {
    throw e
  } catch (e: Throwable) {
    Result.failure(e)
  }

これにより、Coroutineがキャンセルされた場合はCancellationExceptionが正しく上位に伝播します。

利用例

runCatchingの代わりに、そのまま置き換えて使用できます。

suspend fun fetch(): Result<String> =
  runSuspendCatching {
    someFunction(1000)
    "ok"
  }

ネットワークエラーなどはResult.failureとして扱われ、キャンセル時のみ処理全体が正しく中断されます。

CancellationExceptionを握りつぶした場合の問題

CancellationExceptionはCoroutineの制御を担う例外です。
これを握りつぶすと、以下のような問題が発生しやすくなります。

  • 本来停止すべき処理が継続する
  • タイムアウトやスコープキャンセルが正しく機能しない
  • ログや挙動の追跡が困難になる

おわりに

実は本件は、AIによるコードレビューで判明したものです :sweat:
suspend 内で runSuspendCatching を呼び出すことになるため気持ちの良い対応とは言えません。
早く本格対処が進むことを願いします。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?