Spec-Zone.ru › Kotlin 2

Отладка корутин

Отладка приложений, использующих корутины, может быть непростой задачей: несколько корутин могут выполняться одновременно, приостанавливаться в одном потоке и возобновляться в другом. Порядок их выполнения и используемые ими потоки также могут меняться от запуска к запуску, из-за чего трудно отслеживать выполнение конкретной корутины.

На JVM можно использовать следующие возможности, чтобы упростить отладку корутин:

  • Режим отладки присваивает каждой корутине уникальное имя, чтобы её можно было определить в отладчике и диагностическом выводе.

  • Восстановление трассировки стека добавляет информацию о том, где корутина получает исключение вместо ожидаемого результата.

  • Агент отладки отслеживает активные корутины, сообщает об их состоянии и выполняет другие действия.

Режим отладки и восстановление трассировки стека доступны в модуле kotlinx-coroutines-core. Агент отладки доступен в модуле kotlinx-coroutines-debug.

Агент отладки не поддерживается на Android.

Включение режима отладки

Режим отладки присваивает уникальное имя каждой запущенной корутине. Имена корутин можно увидеть в отладчике Java, в строковом представлении корутины и в имени потока, пока он выполняет корутину. Накладные расходы режима отладки во время выполнения незначительны, поэтому его можно оставить включённым, чтобы упростить ведение журналов и диагностику.

При запуске кода с включёнными утверждениями Java библиотека kotlinx.coroutines автоматически включает режим отладки. Модульные тесты по умолчанию запускаются с включёнными утверждениями, поэтому для них не нужно включать режим отладки отдельно.

Чтобы явно включить режим отладки, настройте инструмент сборки, например Gradle или Maven, либо конфигурацию запуска IDE так, чтобы аргумент -Dkotlinx.coroutines.debug передавался JVM, в которой выполняется приложение.

Чтобы включить режим отладки в IntelliJ IDEA, выполните следующие действия:

  1. В виджете Run выберите конфигурацию запуска/отладки, которую хотите изменить, затем выберите Другие действия | Изменить:

    Selecting Edit from the More Actions menu for a run configuration in IntelliJ IDEA

    Если у вас нет конфигурации запуска/отладки, выберите Текущий файл в виджете Run, затем выберите Другие действия | Запустить с параметрами, чтобы открыть настройки конфигурации запуска.

    Selecting Run with Parameters from the More Actions menu for a run configuration in IntelliJ IDEA
  2. В диалоговом окне Конфигурации запуска/отладки введите -Dkotlinx.coroutines.debug в поле Параметры виртуальной машины и нажмите ОК:

    Adding the -Dkotlinx.coroutines.debug option to a run/debug configuration in IntelliJ IDEA

Восстановление трассировки стека

Когда корутина получает исключение из другой корутины через приостанавливающую функцию, например Deferred.await(), трассировка стека исключения не содержит кадров стека принимающей корутины. Без этих кадров стека трассировка не показывает, где вызывается Deferred.await() и какие функции приводят к этому вызову, что может затруднить отладку.

Библиотека kotlinx.coroutines добавляет эту информацию с помощью восстановления трассировки стека, создавая копию исключения с дополнительными кадрами стека.

При возобновлении принимающая корутина выбрасывает копию, а не исходное исключение. Исходное исключение становится причиной копии. Если у исходного исключения есть подавленные исключения, они остаются связанными с ним, а не копируются. Сохранение их связи с исходным исключением помогает предотвратить циклы в цепочке исключений и сбои в некоторых фреймворках.

В режиме отладки восстановление трассировки стека включено по умолчанию. Чтобы отключить восстановление трассировки стека в режиме отладки, передайте параметр VM -Dkotlinx.coroutines.stacktrace.recovery=false.

Ниже приведён пример, показывающий разницу между трассировками стека с включённым и отключённым восстановлением трассировки стека:

import kotlinx.coroutines.*

object UserProfileService :
    CoroutineScope by CoroutineScope(CoroutineName("UserProfileService")) {

    private fun parseUserProfile(): String {
        error("Invalid user profile")
    }

    private fun loadUserProfile(): String {
        return parseUserProfile()
    }

    // Runs in the coroutine that calls this function
    suspend fun awaitUserProfile() {
        // Starts a new coroutine
        val userProfile = async(Dispatchers.Default) {
            // The new coroutine throws the exception
            loadUserProfile()
        }

        // The coroutine running awaitUserProfile()
        // receives the exception through the await() function
        userProfile.await()
    }
}

suspend fun main() {
    UserProfileService.awaitUserProfile()
}

В этом примере функция parseUserProfile() выбрасывает исключение в корутине, запущенной функцией-билдером .async(). Корутина, вызывающая awaitUserProfile(), получает исключение через функцию Deferred.await().

При отключённом восстановлении трассировки стека трассировка показывает, где функция parseUserProfile() выбрасывает исключение в корутине, созданной функцией .async(), но не содержит вызов Deferred.await() в функции awaitUserProfile():

Exception stack trace without stack frames from the receiving coroutine

При включённом восстановлении трассировки стека трассировка также содержит вызов Deferred.await() в функции awaitUserProfile():

Recovered exception stack trace with stack frames from the receiving coroutine

Восстановление трассировки стека для пользовательских исключений

Восстановление трассировки стека может автоматически копировать исключение, если его класс имеет открытый конструктор, принимающий сообщение, причину, оба аргумента или вовсе не принимающий аргументов.

Если вы хотите, чтобы библиотека kotlinx.coroutines восстанавливала трассировку стека исключения, для которого требуются дополнительные аргументы конструктора, например номер строки или код ошибки, реализуйте интерфейс StackTraceRecoverable.

Интерфейс StackTraceRecoverable входит в стандартную библиотеку Kotlin, поэтому его можно реализовать, не добавляя зависимость от библиотеки kotlinx.coroutines.

Интерфейс StackTraceRecoverable доступен на всех целевых платформах, но библиотека kotlinx.coroutines использует его для восстановления трассировки стека только на JVM.

Чтобы реализовать интерфейс, переопределите функцию copyForStackTraceRecovery(). В переопределённой функции верните новый экземпляр исключения для восстановления трассировки стека или null, если не хотите, чтобы библиотека kotlinx.coroutines копировала исключение.

Эти API являются экспериментальными и требуют явного согласия с помощью аннотации @OptIn(ExperimentalStdlibCoroutineSupportApi::class).

Ниже приведён пример пользовательского исключения, которое сохраняет свойство line при создании нового экземпляра для восстановления трассировки стека:

import kotlinx.coroutines.*
import kotlin.coroutines.ExperimentalStdlibCoroutineSupportApi
import kotlin.coroutines.debug.StackTraceRecoverable

@OptIn(ExperimentalStdlibCoroutineSupportApi::class)
class FileEditException
// The implementation requires a private constructor
// to pass the cause to the IllegalStateException constructor
private constructor(
    val line: Int,
    private val detail: String,
    cause: Throwable?,
) : IllegalStateException("When editing line $line: $detail", cause),
    // Implements StackTraceRecoverable for stack trace recovery
    StackTraceRecoverable<FileEditException> {

    constructor(line: Int, detail: String) : this(line, detail, null)

    // Copies the line number and message details
    override fun copyForStackTraceRecovery(): FileEditException =
        FileEditException(line, detail, this)
}

private fun editFile() {
    throw FileEditException(15, "Unexpected token")
}

suspend fun main() {
    supervisorScope {
        // Starts a new coroutine
        val fileEdit = async(Dispatchers.Default) {
            // Throws the original exception
            editFile()
        }
        
        // Stack trace recovery creates a copy of the exception,
        // adds the calling coroutine's stack frames, and throws the copy
        fileEdit.await()
    }
}

При включённом режиме отладки вывод содержит восстановленную копию, за которой следует исходное исключение в качестве причины.

Exception in thread "main" com.example.FileEditException: When editing line 15: Unexpected token at com.example.RecoveryExampleKt.editFile(RecoveryExample.kt:54) at com.example.RecoveryExampleKt.access$editFile(RecoveryExample.kt:1) at com.example.RecoveryExampleKt$main$2$fileEdit$1.invokeSuspend(RecoveryExample.kt:62) at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt:42) at com.example.RecoveryExampleKt$main$2.invokeSuspend(RecoveryExample.kt:67) Caused by: com.example.FileEditException: When editing line 15: Unexpected token at com.example.RecoveryExampleKt.editFile(RecoveryExample.kt:54) at com.example.RecoveryExampleKt.access$editFile(RecoveryExample.kt:1) at com.example.RecoveryExampleKt$main$2$fileEdit$1.invokeSuspend(RecoveryExample.kt:62) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100) at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:807) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)

Агент отладки

Модуль kotlinx-coroutines-debug предоставляет агент отладки для приложений JVM. Агент отслеживает создание, приостановку и возобновление корутин.

API DebugProbes служит основной точкой входа для агента отладки. С его помощью можно выводить активные корутины и их текущее состояние. Вывод содержит трассировки стека, показывающие, где была создана каждая корутина и где она приостановлена. Также с его помощью можно вывести дамп корутин для иерархии конкретного Job или CoroutineScope.

Если включить DebugProbes в рабочей среде, производительность приложения может значительно снизиться, поскольку для каждой новой корутины будет создаваться трассировка стека. Чтобы избежать этих накладных расходов, установите для DebugProbes.enableCreationStackTraces значение false.

Модуль kotlinx-coroutines-debug обеспечивает автоматическую интеграцию с BlockHound. С его помощью можно обнаруживать блокирующие операции в контекстах корутин, где они запрещены.

Инструкции по настройке см. в кратком руководстве по BlockHound.

Добавление зависимости агента отладки

Чтобы использовать агент отладки в проекте, добавьте зависимость kotlinx-coroutines-debug:

// build.gradle.kts
dependencies {
    testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.11.0")
}
<!-- pom.xml -->
<dependency>
    <groupId>org.jetbrains.kotlinx</groupId>
    <artifactId>kotlinx-coroutines-debug</artifactId>
    <version>1.11.0</version>
    <scope>test</scope>
</dependency>

Отслеживание корутин с помощью агента отладки

Чтобы начать отслеживать корутины с помощью агента отладки, можно:

  • Добавить -javaagent:/path/to/kotlinx-coroutines-debug-1.11.0.jar к параметрам VM, чтобы загрузить агент отладки при запуске приложения.

  • Вызвать функцию DebugProbes.install() перед запуском корутин, которые нужно отслеживать.

Начиная с JDK 21 динамическая загрузка агента отладки с помощью функции DebugProbes.install() может привести к появлению предупреждения. Чтобы избежать его, загрузите агент с помощью параметра VM -javaagent.

Когда агент отладки активен, можно использовать следующие API:

  • DebugProbes.dumpCoroutines() выводит все активные корутины.

  • DebugProbes.dumpCoroutinesInfo() возвращает информацию об активных корутинах.

  • DebugProbes.printJob() выводит дамп корутин для иерархии Job.

  • DebugProbes.printScope() выводит дамп корутин для иерархии CoroutineScope.

Ниже приведён пример использования агента отладки для вывода активных корутин и иерархии корутин для конкретного Job:

import kotlinx.coroutines.*
import kotlinx.coroutines.debug.*
import kotlin.time.Duration.Companion.seconds

private suspend fun loadAccount() {
    delay(5.seconds)
}

private suspend fun loadPreferences() {
    delay(5.seconds)
}

private suspend fun loadUserProfile() = coroutineScope {
    launch { loadAccount() }
    launch { loadPreferences() }
}

@OptIn(ExperimentalCoroutinesApi::class)
fun main() {
    // Installs the debug agent
    // This is only required if you don't use the -javaagent VM option
    DebugProbes.install()

    runBlocking {
        // Starts a coroutine with two child coroutines
        val loadingJob = launch {
            loadUserProfile()
        }

        // Gives the child coroutines time to suspend
        delay(1.seconds)

        // Prints all active coroutines
        DebugProbes.dumpCoroutines()

        println("============")

        // Prints the loading job and its child coroutines
        DebugProbes.printJob(loadingJob)
    }
}

Если включён режим отладки, пример выведет следующие данные:

Coroutines dump 2026/08/18 14:00:08 Coroutine "coroutine#1":BlockingCoroutine{Active}@146ba0ac, state: RUNNING at java.base/java.lang.Thread.getStackTrace(Thread.java:2389) at kotlinx.coroutines.debug.internal.DebugProbesImpl.enhanceStackTraceWithThreadDumpImpl(DebugProbesImpl.kt:339) at kotlinx.coroutines.debug.internal.DebugProbesImpl.dumpCoroutinesSynchronized(DebugProbesImpl.kt:294) at kotlinx.coroutines.debug.internal.DebugProbesImpl.dumpCoroutines(DebugProbesImpl.kt:266) at kotlinx.coroutines.debug.DebugProbes.dumpCoroutines(DebugProbes.kt:181) at kotlinx.coroutines.debug.DebugProbes.dumpCoroutines$default(DebugProbes.kt:181) at DebugAgentExampleKt$main$1.invokeSuspend(DebugAgentExample.kt:34) Coroutine "coroutine#2":StandaloneCoroutine{Active}@4dfa3a9d, state: SUSPENDED at DebugAgentExampleKt$main$1$loadingJob$1.invokeSuspend(DebugAgentExample.kt:27) Coroutine "coroutine#3":StandaloneCoroutine{Active}@6eebc39e, state: SUSPENDED at DebugAgentExampleKt$loadUserProfile$2$1.invokeSuspend(DebugAgentExample.kt:14) Coroutine "coroutine#4":StandaloneCoroutine{Active}@464bee09, state: SUSPENDED at DebugAgentExampleKt$loadUserProfile$2$2.invokeSuspend(DebugAgentExample.kt:15)============ "coroutine#2":StandaloneCoroutine{Active}, continuation is SUSPENDED at line DebugAgentExampleKt$main$1$loadingJob$1.invokeSuspend(DebugAgentExample.kt:27) "coroutine#3":StandaloneCoroutine{Active}, continuation is SUSPENDED at line DebugAgentExampleKt$loadUserProfile$2$1.invokeSuspend(DebugAgentExample.kt:14) "coroutine#4":StandaloneCoroutine{Active}, continuation is SUSPENDED at line DebugAgentExampleKt$loadUserProfile$2$2.invokeSuspend(DebugAgentExample.kt:15)

Вывод активных корутин при превышении времени ожидания тестов JUnit

Для тестов JUnit можно задать время ожидания с помощью соответствующего API CoroutinesTimeout, в зависимости от версии JUnit. API автоматически устанавливает диагностические пробы. Если тест не завершится до истечения времени ожидания, он выведет все активные корутины и их трассировки стека, а затем завершится с ошибкой.

JUnit 4

Чтобы задать время ожидания для тестов JUnit 4 и вывести все активные корутины и их трассировки стека при его превышении, используйте правило CoroutinesTimeout:

import kotlinx.coroutines.*
import kotlinx.coroutines.debug.junit4.CoroutinesTimeout
import org.junit.Rule
import org.junit.Test
import kotlin.time.Duration

@OptIn(ExperimentalCoroutinesApi::class)
class UserProfileTest {
    @get:Rule
    val timeout = CoroutinesTimeout.seconds(1)

    private suspend fun loadUserProfile() {
        withContext(Dispatchers.IO) {
            // Simulates an operation that doesn't complete
            delay(Duration.INFINITE)
        }
    }

    @Test
    fun loadsUserProfile() = runBlocking {
        val loadingJob = launch {
            loadUserProfile()
        }

        // Waits for the coroutine, so the test doesn't complete
        loadingJob.join()
    }
}

Через одну секунду правило сообщает, что время ожидания теста истекло, и выводит все активные корутины и их трассировки стека. Затем тест завершается с ошибкой TestTimedOutException.

Test loadsUserProfile timed out after 1 seconds Coroutines dump 2026/08/18 13:48:21 Coroutine "coroutine#1":BlockingCoroutine{Active}@bf1ec20, state: SUSPENDED at UserProfileTest$loadsUserProfile$1.invokeSuspend(UserProfileTest.kt:27) at _COROUTINE._CREATION._(CoroutineDebugging.kt:30) at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:161) at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26) at kotlinx.coroutines.BuildersKt__Builders_concurrentKt.runBlockingK$default(Builders.concurrent.kt:157) at kotlinx.coroutines.BuildersKt.runBlockingK$default(Unknown Source) at UserProfileTest.loadsUserProfile(UserProfileTest.kt:21) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at kotlinx.coroutines.debug.junit4.CoroutinesTimeoutStatement$evaluate$$inlined$runWithTimeoutDumpingCoroutines$1.call(CoroutinesTimeoutImpl.kt:79) at kotlinx.coroutines.debug.junit4.CoroutinesTimeoutStatement$evaluate$$inlined$runWithTimeoutDumpingCoroutines$1.call(CoroutinesTimeoutImpl.kt:79) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) at java.base/java.lang.Thread.run(Thread.java:1575) Coroutine "coroutine#2":StandaloneCoroutine{Active}@70efb718, state: SUSPENDED at UserProfileTest$loadUserProfile$2.invokeSuspend(UserProfileTest.kt:16) at UserProfileTest$loadsUserProfile$1$loadingJob$1.invokeSuspend(UserProfileTest.kt:23) at _COROUTINE._CREATION._(CoroutineDebugging.kt:30) at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:161) at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26) at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:200) at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source) at UserProfileTest$loadsUserProfile$1.invokeSuspend(UserProfileTest.kt:22) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100) at kotlinx.coroutines.BuildersKt__Builders_concurrentKt.runBlockingK$default(Builders.concurrent.kt:157) at kotlinx.coroutines.BuildersKt.runBlockingK$default(Unknown Source) at UserProfileTest.loadsUserProfile(UserProfileTest.kt:21) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at kotlinx.coroutines.debug.junit4.CoroutinesTimeoutStatement$evaluate$$inlined$runWithTimeoutDumpingCoroutines$1.call(CoroutinesTimeoutImpl.kt:79) at kotlinx.coroutines.debug.junit4.CoroutinesTimeoutStatement$evaluate$$inlined$runWithTimeoutDumpingCoroutines$1.call(CoroutinesTimeoutImpl.kt:79) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) at java.base/java.lang.Thread.run(Thread.java:1575) test timed out after 1000 milliseconds org.junit.runners.model.TestTimedOutException: test timed out after 1000 milliseconds at java.base/jdk.internal.misc.Unsafe.park(Native Method) at java.base/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:269) at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:57) at kotlinx.coroutines.BuildersKt__BuildersKt.runBlockingImpl(Builders.kt:30) at kotlinx.coroutines.BuildersKt__BuildersKt.runBlockingImpl(Unknown Source) at kotlinx.coroutines.BuildersKt__Builders_concurrentKt.runBlockingK(Builders.concurrent.kt:172) at kotlinx.coroutines.BuildersKt.runBlockingK(Unknown Source) at kotlinx.coroutines.BuildersKt__Builders_concurrentKt.runBlockingK$default(Builders.concurrent.kt:157) at kotlinx.coroutines.BuildersKt.runBlockingK$default(Unknown Source) at UserProfileTest.loadsUserProfile(UserProfileTest.kt:21) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at kotlinx.coroutines.debug.junit4.CoroutinesTimeoutStatement$evaluate$$inlined$runWithTimeoutDumpingCoroutines$1.call(CoroutinesTimeoutImpl.kt:79) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) at java.base/java.lang.Thread.run(Thread.java:1575)

JUnit 5

Чтобы задать время ожидания для всех тестовых функций класса, добавьте к классу аннотацию @CoroutinesTimeout:

import kotlinx.coroutines.*
import kotlinx.coroutines.debug.junit5.CoroutinesTimeout
import org.junit.jupiter.api.Test
import kotlin.time.Duration

@OptIn(ExperimentalCoroutinesApi::class)
// Sets a one-second timeout for all test functions in the class
@CoroutinesTimeout(testTimeoutMs = 1_000)
class UserProfileTest {
    private suspend fun loadUserProfile() {
        withContext(Dispatchers.IO) {
            // Simulates an operation that doesn't complete
            delay(Duration.INFINITE)
        }
    }

    @Test
    fun loadsUserProfile() = runBlocking {
        val loadingJob = launch {
            loadUserProfile()
        }

        // Waits for the coroutine, so the test doesn't complete
        loadingJob.join()
    }
}

Через одну секунду API CoroutinesTimeout сообщает об истечении времени ожидания и выводит все активные корутины и их трассировки стека. Затем тест завершается с ошибкой CoroutinesTimeoutException.

Test loadsUserProfile timed out after 1 seconds Coroutines dump 2026/08/18 13:46:15 Coroutine "coroutine#1":BlockingCoroutine{Active}@5c77053b, state: SUSPENDED at UserProfileTest$loadsUserProfile$1.invokeSuspend(UserProfileTest.kt:24) Coroutine "coroutine#2":StandaloneCoroutine{Active}@26b894bd, state: SUSPENDED at UserProfileTest$loadUserProfile$2.invokeSuspend(UserProfileTest.kt:13) at UserProfileTest$loadsUserProfile$1$loadingJob$1.invokeSuspend(UserProfileTest.kt:20) test timed out after 1000 ms kotlinx.coroutines.debug.junit5.CoroutinesTimeoutException: test timed out after 1000 ms at java.base/jdk.internal.misc.Unsafe.park(Native Method) at java.base/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:269) at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:57) at kotlinx.coroutines.BuildersKt__BuildersKt.runBlockingImpl(Builders.kt:30) at kotlinx.coroutines.BuildersKt__BuildersKt.runBlockingImpl(Unknown Source) at kotlinx.coroutines.BuildersKt__Builders_concurrentKt.runBlockingK(Builders.concurrent.kt:172) at kotlinx.coroutines.BuildersKt.runBlockingK(Unknown Source) at kotlinx.coroutines.BuildersKt__Builders_concurrentKt.runBlockingK$default(Builders.concurrent.kt:157) at kotlinx.coroutines.BuildersKt.runBlockingK$default(Unknown Source) at UserProfileTest.loadsUserProfile(UserProfileTest.kt:18) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at kotlinx.coroutines.debug.junit5.CoroutinesTimeoutExtension$interceptInvocation$$inlined$runWithTimeoutDumpingCoroutines$1.call(CoroutinesTimeoutImpl.kt:79) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) at java.base/java.lang.Thread.run(Thread.java:1575)

Устранение конфликтов ресурсов kotlinx-coroutines-debug на Android

Агент отладки не поддерживается на Android.

Модуль kotlinx-coroutines-debug имеет транзитивные зависимости от JNA, JNA Platform, Byte Buddy и Byte Buddy Agent. Некоторые из этих зависимостей содержат ресурсы с одинаковыми путями. При объединении Android ресурсов зависимостей дублирующиеся пути могут вызвать DuplicateRelativeFileException и привести к сбою сборки.

Чтобы устранить сбой сборки и сохранить зависимость kotlinx-coroutines-debug, исключите конфликтующие ресурсы с помощью следующей конфигурации packaging в файле build.gradle.kts:

// build.gradle.kts
android {
    packaging {
        resources {
            // Excludes license files from JNA and JNA Platform
            excludes += setOf(
                "META-INF/AL2.0",
                "META-INF/LGPL2.1",
            )

            // Excludes the ASM license file from Byte Buddy
            excludes += "META-INF/licenses/ASM"

            // Retains one copy of each Byte Buddy Agent file
            pickFirsts += setOf(
                "win32-x86-64/attach_hotspot_windows.dll",
                "win32-x86/attach_hotspot_windows.dll",
            )
        }
    }
}

Что дальше

Узнайте, как отлаживать корутины в IntelliJ IDEA, в руководствах Отладка корутин с помощью IntelliJ IDEA и Отладка Kotlin Flow с помощью IntelliJ IDEA.

7 сентября 2026 г.
Выражение select (экспериментальное)Отладка корутин с помощью IntelliJ IDEA — руководство

© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/coroutines-debugging.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API