-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Attach coroutine debug agent safely #4524
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea of providing better error messages when the debug agent failed to attach is good. The proposed solution looks too complex to me, but maybe I'm missing some context.
// If this property is disabled, the debug agent will not be applied at all | ||
val applyDebugAgent = System.getProperty("kotlinx.coroutines.apply.debug.agent", "true").toBoolean() | ||
if (!applyDebugAgent) return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If someone controls the system environment at the point when premain
is being run, it means they can edit the command line which starts the process, and then, they can simply avoid specifying the Java agent. What's the purpose of adding a new property, then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, we don't need a property, we can just add the hint to remove the -javaagent
argument in the error message
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed this extra system property
* If `kotlinx.coroutines.apply.debug.agent` is set to `false`, the coroutine debug agent will not be applied. | ||
*/ | ||
@Suppress("unused") | ||
internal object SafeAgentPremain { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand the purpose of this wrapper. What's stopping us from adding the check to AgentPremain
itself? Won't it be enough to call something like this function as the first thing in AgentPremain.premain
?
private fun checkIfStdlibIsAvailable() {
try {
Result.success(42)
} catch (_: Throwable) {
error("kotlinx.coroutines debug agent failed to load " +
"because the Kotlin standard library is not present in the classpath.")
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense, yes) This is the first point when we find out that stdlib is not loaded
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If someone later adds a field of some stdlib type to the AgentPremain class, it will cause problems at class loading time. The wrapper will protect against that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, actually this does not help, because the error happens when the AgentPremain
class is trying to initialize, at the moment AgentPremain.<clinit>
is called. And this check in premain
is already too late.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To protect against that, we could implement the check in AgentPremain
like so:
// This should be the first property, to ensure the check happens first! Add new properties only below!
val dummy = checkIfStdlibIsAvailable()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And replaced the wrapper class with this dummy property check
… if initialization of AgentPremain failed.
3a8fb3f
to
bed7062
Compare
please also add a test to ensure that the agent does no harm if stdlib is not available |
… case no stdlib dependency was found
integration-testing/build.gradle.kts
Outdated
// 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." | ||
create<Test>("safeDebugAgentTest") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dkhalanskyjb hi!)
I need some help with Gradle here 👀
How could we check, that :safeDebugAgentTest:run
fails with an error from AgentPremain?
Is there smth like assertFailsWith
in Gradle?
I've thought of an alternative to run :safeDebugAgentTest:run
from a Kotlin test: to create a ProcessBuilder
+ try / catch, but maybe there is an easier option?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm no Gradle expert, either. I've seen this approach to checking whether and how a Gradle task fails:
- https://github.com/Kotlin/kotlinx-benchmark/blob/bb2208c76f819056e1fa0e665dee503249d4650e/integration/src/test/kotlin/kotlinx/benchmark/integration/BenchmarkFunctionNameValidationTest.kt the test itself,
- https://github.com/Kotlin/kotlinx-benchmark/blob/bb2208c76f819056e1fa0e665dee503249d4650e/integration/src/test/resources/templates/funny-names the project whose build should fail,
- https://github.com/Kotlin/kotlinx-benchmark/blob/bb2208c76f819056e1fa0e665dee503249d4650e/integration/build.gradle.kts#L15 the required dependency.
I think this should point you in the right direction, but if you encounter any difficulties, please let me know, and I'll look into this more closely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also possibly @shanshin could help?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you like to check the cause of the failure in functional tests or directly in the build?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to ensure that the run
task of the project fails (:safeDebugAgentTest:run
), I guess those examples from the benchmarks should help, I'll try
Thank you)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, I decided to avoid gradle test runners, that would be too complicated. Just forwarded errorOutput of the gradle task and added an error message check.
@@ -0,0 +1 @@ | |||
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=2g No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And here I assumed that the error on TC: JVM crashed (This problem is reported because OutOfMemoryError has been found in the build log text)
could be fixed with more heap space, but I'm not sure
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be a mistake on the part of TeamCity. The only places in the build log where I see the OutOfMemoryError
are this message that you quote and -XX:+HeapDumpOnOutOfMemoryError
, which technically contains the string OutOfMemoryError
but does not indicate any problems.
Recently there was a bug in the debugger IDEA-377586: the debug session failed at the start because initialization of the coroutine debug agent failed with this error:
This happened because debugger applied
kotlinx-coroutines-core
as a-javaagent
to the run configuration, which did not havekotlin-stdlib
in it's bootstrap classpath and the Kotlin classes would've only be loaded later.Ad a WA the debugger just skips run configurations like this and does not try to attach the coroutine debug agent at all.
This issue relates to the main problem of the coroutines agent attach (#4469), and SPI solution would help in this case too.
For now I suggest to provide some "safe" wrapper around
AgentPremain
, which would do nothing in caseAgentPremain
initialization fails.It would be bad to fail completely silently, as the user would want to know, what could be done to attach the agent anyway, e.g. add
kotlin-stdlib.jar
to the bootstrap classpath or smth. So maybe we can choose silent failure / clear error message by setting some system property.Anyway, I think it makes sense to provide some readable clear error message if something went wrong and give a hint how to attach the agent, because the debugger does not help in these cases.
If there may be a better solution, let's discuss please)