diff --git a/integration-testing/build.gradle.kts b/integration-testing/build.gradle.kts index b84cbc453b..66ab6ee5c1 100644 --- a/integration-testing/build.gradle.kts +++ b/integration-testing/build.gradle.kts @@ -233,7 +233,7 @@ tasks { ":jpmsTest:check", "smokeTest:build", "java8Test:check", - "safeDebugAgentTest:runWithExpectedFailure" + "safeDebugAgentTest:attachAgentWithoutKotlinStdlib" ) } diff --git a/integration-testing/safeDebugAgentTest/build.gradle.kts b/integration-testing/safeDebugAgentTest/build.gradle.kts index a4e010ba21..f44279dcd0 100644 --- a/integration-testing/safeDebugAgentTest/build.gradle.kts +++ b/integration-testing/safeDebugAgentTest/build.gradle.kts @@ -15,29 +15,27 @@ application { // In this test coroutine debug agent is attached as a javaagent vm argument // to a pure Java project (safeDebugAgentTest) with no Kotlin stdlib dependency. -// In this case the error should be thrown from AgetnPremain class: -// "java.lang.IllegalStateException: kotlinx.coroutines debug agent failed to load." -tasks.register("runWithExpectedFailure") { - val agentJar = System.getProperty("coroutines.debug.agent.path") - val errorOutputStream = ByteArrayOutputStream() - val standardOutputStream = ByteArrayOutputStream() +tasks.register("attachAgentWithoutKotlinStdlib") { + dependsOn("classes") - project.javaexec { - mainClass.set("Main") - classpath = sourceSets.main.get().runtimeClasspath - jvmArgs = listOf("-javaagent:$agentJar") - errorOutput = errorOutputStream - standardOutput = standardOutputStream - isIgnoreExitValue = true - } + doLast { + val agentJar = System.getProperty("coroutines.debug.agent.path") + val errorOutputStream = ByteArrayOutputStream() + val standardOutputStream = ByteArrayOutputStream() + + project.javaexec { + mainClass.set("Main") + classpath = sourceSets.main.get().runtimeClasspath + jvmArgs = listOf("-javaagent:$agentJar") + errorOutput = errorOutputStream + standardOutput = standardOutputStream + } - val expectedAgentError = - "kotlinx.coroutines debug agent failed to load.\n" + - "Please ensure that the Kotlin standard library is present in the classpath.\n" + - "Alternatively, you can disable kotlinx.coroutines debug agent by removing `-javaagent=/path/kotlinx-coroutines-core.jar` from your VM arguments.\n" - val errorOutput = errorOutputStream.toString() - val standardOutput = standardOutputStream.toString() - if (!errorOutput.contains(expectedAgentError)) { - throw GradleException("':safeDebugAgentTest:runWithExpectedFailure' completed with an unexpected output:\n" + standardOutput + "\n" + errorOutput) + check(errorOutputStream.toString().isEmpty()) { + "Expected no output in the error stream, but got:\n$errorOutputStream" + } + check(standardOutputStream.toString().contains("OK!")) { + "Expected 'OK!' in the standard output, but got:\n$standardOutputStream" + } } -} \ No newline at end of file +} diff --git a/integration-testing/safeDebugAgentTest/src/main/java/Main.java b/integration-testing/safeDebugAgentTest/src/main/java/Main.java index c48e678864..17010eb2db 100644 --- a/integration-testing/safeDebugAgentTest/src/main/java/Main.java +++ b/integration-testing/safeDebugAgentTest/src/main/java/Main.java @@ -2,4 +2,4 @@ public class Main { public static void main(String[] args) { System.out.printf("OK!"); } -} \ No newline at end of file +} diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/AgentPremain.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/AgentPremain.kt index 92786c795d..d6aa65ed4b 100644 --- a/kotlinx-coroutines-core/jvm/src/debug/internal/AgentPremain.kt +++ b/kotlinx-coroutines-core/jvm/src/debug/internal/AgentPremain.kt @@ -3,7 +3,6 @@ package kotlinx.coroutines.debug.internal import android.annotation.* import org.codehaus.mojo.animal_sniffer.* import sun.misc.* -import java.lang.IllegalStateException import java.lang.instrument.* import java.lang.instrument.ClassFileTransformer import java.security.* @@ -16,30 +15,12 @@ import java.security.* @SuppressLint("all") @IgnoreJRERequirement // Never touched on Android internal object AgentPremain { - - // This should be the first property to ensure the check happens first! Add new properties only below! - private val dummy = checkIfStdlibIsAvailable() - - /** - * This check ensures that kotlin-stdlib classes are loaded by the time AgentPremain is initialized; - * otherwise the debug session would fail with `java.lang.NoClassDefFoundError: kotlin/Result`. - */ - private fun checkIfStdlibIsAvailable() { - try { - Result.success(42) - } catch (t: Throwable) { - throw IllegalStateException("kotlinx.coroutines debug agent failed to load.\n" + - "Please ensure that the Kotlin standard library is present in the classpath.\n" + - "Alternatively, you can disable kotlinx.coroutines debug agent by removing `-javaagent=/path/kotlinx-coroutines-core.jar` from your VM arguments.\n" + - t.cause - ) - } + private val enableCreationStackTraces = try { + System.getProperty("kotlinx.coroutines.debug.enable.creation.stack.trace")?.toBoolean()!! + } catch (_: Throwable) { + DebugProbesImpl.enableCreationStackTraces } - private val enableCreationStackTraces = runCatching { - System.getProperty("kotlinx.coroutines.debug.enable.creation.stack.trace")?.toBoolean() - }.getOrNull() ?: DebugProbesImpl.enableCreationStackTraces - @JvmStatic @Suppress("UNUSED_PARAMETER") fun premain(args: String?, instrumentation: Instrumentation) { diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/ConcurrentWeakMap.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/ConcurrentWeakMap.kt index f28d9a6d42..97089d9fc2 100644 --- a/kotlinx-coroutines-core/jvm/src/debug/internal/ConcurrentWeakMap.kt +++ b/kotlinx-coroutines-core/jvm/src/debug/internal/ConcurrentWeakMap.kt @@ -3,6 +3,7 @@ package kotlinx.coroutines.debug.internal import kotlinx.atomicfu.* import kotlinx.coroutines.internal.* import java.lang.ref.* +import java.util.AbstractMap // This is very limited implementation, not suitable as a generic map replacement. // It has lock-free get and put with synchronized rehash for simplicity (and better CPU usage on contention) @@ -13,7 +14,7 @@ internal class ConcurrentWeakMap( * reference to the value when the key was already disposed. */ weakRefQueue: Boolean = false -) : AbstractMutableMap() { +) : AbstractMap() { private val _size = atomic(0) private val core = atomic(Core(MIN_CAPACITY)) private val weakRefQueue: ReferenceQueue? = if (weakRefQueue) ReferenceQueue() else null @@ -68,7 +69,7 @@ internal class ConcurrentWeakMap( while (true) { cleanWeakRef(weakRefQueue.remove() as HashedWeakRef<*>) } - } catch (e: InterruptedException) { + } catch (_: InterruptedException) { Thread.currentThread().interrupt() } } diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt index 9583d7e766..979ccfb221 100644 --- a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt +++ b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt @@ -50,11 +50,13 @@ internal object DebugProbesImpl { private val dynamicAttach = getDynamicAttach() @Suppress("UNCHECKED_CAST") - private fun getDynamicAttach(): Function1? = runCatching { + private fun getDynamicAttach(): Function1? = try { val clz = Class.forName("kotlinx.coroutines.debug.ByteBuddyDynamicAttach") val ctor = clz.constructors[0] ctor.newInstance() as Function1 - }.getOrNull() + } catch (_: Throwable) { + null + } /** * Because `probeCoroutinesResumed` is called for every resumed continuation (see KT-29997 and the related code), @@ -88,8 +90,14 @@ internal object DebugProbesImpl { } private fun startWeakRefCleanerThread() { - weakRefCleanerThread = thread(isDaemon = true, name = "Coroutines Debugger Cleaner") { + // Can not use the `thread { }` function here, as the standard library may be unavailable + // when the debug agent is loaded. + Thread({ callerInfoCache.runWeakRefQueueCleaningLoopUntilInterrupted() + }, "Coroutines Debugger Cleaner").also { + weakRefCleanerThread = it + it.isDaemon = true + it.start() } } @@ -194,7 +202,7 @@ internal object DebugProbesImpl { "dispatcher": $dispatcher, "sequenceNumber": ${info.sequenceNumber}, "state": "${info.state}" - } + } """.trimIndent() ) lastObservedFrames.add(info.lastObservedFrame)