Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion integration-testing/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ tasks {
":jpmsTest:check",
"smokeTest:build",
"java8Test:check",
"safeDebugAgentTest:runWithExpectedFailure"
"safeDebugAgentTest:attachAgentWithoutKotlinStdlib"
)
}

Expand Down
44 changes: 21 additions & 23 deletions integration-testing/safeDebugAgentTest/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Test>("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"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ public class Main {
public static void main(String[] args) {
System.out.printf("OK!");
}
}
}
27 changes: 4 additions & 23 deletions kotlinx-coroutines-core/jvm/src/debug/internal/AgentPremain.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -13,7 +14,7 @@ internal class ConcurrentWeakMap<K : Any, V: Any>(
* reference to the value when the key was already disposed.
*/
weakRefQueue: Boolean = false
) : AbstractMutableMap<K, V>() {
) : AbstractMap<K, V>() {
private val _size = atomic(0)
private val core = atomic(Core(MIN_CAPACITY))
private val weakRefQueue: ReferenceQueue<K>? = if (weakRefQueue) ReferenceQueue() else null
Expand Down Expand Up @@ -68,7 +69,7 @@ internal class ConcurrentWeakMap<K : Any, V: Any>(
while (true) {
cleanWeakRef(weakRefQueue.remove() as HashedWeakRef<*>)
}
} catch (e: InterruptedException) {
} catch (_: InterruptedException) {
Thread.currentThread().interrupt()
}
}
Expand Down
16 changes: 12 additions & 4 deletions kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,13 @@ internal object DebugProbesImpl {
private val dynamicAttach = getDynamicAttach()

@Suppress("UNCHECKED_CAST")
private fun getDynamicAttach(): Function1<Boolean, Unit>? = runCatching {
private fun getDynamicAttach(): Function1<Boolean, Unit>? = try {
val clz = Class.forName("kotlinx.coroutines.debug.ByteBuddyDynamicAttach")
val ctor = clz.constructors[0]
ctor.newInstance() as Function1<Boolean, Unit>
}.getOrNull()
} catch (_: Throwable) {
null
}

/**
* Because `probeCoroutinesResumed` is called for every resumed continuation (see KT-29997 and the related code),
Expand Down Expand Up @@ -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()
}
}

Expand Down Expand Up @@ -194,7 +202,7 @@ internal object DebugProbesImpl {
"dispatcher": $dispatcher,
"sequenceNumber": ${info.sequenceNumber},
"state": "${info.state}"
}
}
""".trimIndent()
)
lastObservedFrames.add(info.lastObservedFrame)
Expand Down