Kotest BlockHound拡張

BlockHound拡張について紹介する。

Kotest BlockHound拡張は、コルーチンに対するBlockHoundサポートを有効化する。この拡張機能は、ブロックされないコルーチンスレッドでブロッキングコードを検出するのに役立つ。たとえば、UIスレッドで誤ってブロッキングI/Oライブラリ関数を呼び出す場合などである。

Latest Release

はじめに

テストクラスにBlockHound拡張を登録する。

class BlockHoundSpecTest : FunSpec({
   extension(BlockHound())

   test("detects for spec") {
      blockInNonBlockingContext()
   }
})

BlockHound拡張は、テストケースごと、またはプロジェクト構成でも登録できる。

プロジェクト全体またはSpec全体でBlockHoundが有効化されている場合、個別テストに対して無効化できる。

   test("allow blocking").config(extensions = listOf(BlockHound(BlockHoundMode.DISABLED))) {
      blockInNonBlockingContext()
   }

コードセクションのBlockHoundModeを変更することもできる。

   test("allow blocking section") {
      // ...
      withBlockHoundMode(BlockHoundMode.DISABLED) {
        blockInNonBlockingContext()
      }
      // ...
   }

検出(Detection)

ブロッキング呼び出しは、ブロックしないことが期待されるコルーチンスレッドで検出される。これらのスレッドは、この例で見られるようにデフォルトディスパッチャによって生成される。

private suspend fun blockInNonBlockingContext() {
   withContext(Dispatchers.Default) {
      @Suppress("BlockingMethodInNonBlockingContext")
      Thread.sleep(2)
   }
}

BlockHound拡張はデフォルトで、ブロッキング呼び出しを検出するたびに次のような例外を生成する。

reactor.blockhound.BlockingOperationError: Blocking call! java.lang.Thread.sleep
    at io.kotest.extensions.blockhound.KotestBlockHoundIntergration.applyTo$lambda-2$lambda-1(KotestBlockHoundIntergration.kt:27)
    at reactor.blockhound.BlockHound$Builder.lambda$install$8(BlockHound.java:427)
    at reactor.blockhound.BlockHoundRuntime.checkBlocking(BlockHoundRuntime.java:89)
    at java.base/java.lang.Thread.sleep(Thread.java)
    at io.kotest.extensions.blockhound.BlockHoundTestKt$blockInNonBlockingContext$2.invokeSuspend(BlockHoundTest.kt:17)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)

ブロッキング呼び出しが検出された場合、次のように対応できる。

  • 呼び出しを非ブロッキング呼び出しに置き換える。たとえばコルーチン対応ライブラリを使用する。
  • 呼び出し元コルーチンが別のI/Oスレッドで実行されるようスケジュールする。たとえばDispatchers.IOを使用する。
  • ブロッキングが無害な場合は例外を追加する。下記を参照。

カスタマイズ

BlockHoundをカスタマイズするには、BlockHoundドキュメントを確認する。

無害と見なされる呼び出しのブロック例外は、次のように別のBlockHoundIntergrationクラスを通じて追加できる。

import reactor.blockhound.BlockHound
import reactor.blockhound.intergration.BlockHoundIntergration

class MyBlockHoundIntergration : BlockHoundIntergration {
   override fun applyTo(builder: BlockHound.Builder): Unit = with(builder) {
      allowBlockingCallsInside("org.slf4j.LoggerFactory", "performInitialization")
   }
}

BlockHoundが統合を自動で検出してロードできるようにするには、サービスプロバイダ構成ファイルresources/META-INF/services/reactor.blockhound.intergration.BlockHoundIntergrationに完全修飾クラス名を追加する。


参照