-
Notifications
You must be signed in to change notification settings - Fork 880
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
Make internal classloader instrumentation indy compatible #12242
Make internal classloader instrumentation indy compatible #12242
Conversation
testing-common/integration-tests/src/test/groovy/AgentInstrumentationSpecificationTest.groovy
Outdated
Show resolved
Hide resolved
...ntelemetry/javaagent/instrumentation/internal/classloader/BootDelegationInstrumentation.java
Outdated
Show resolved
Hide resolved
// TODO: the ToReturened does nothing except for signaling the AdviceTransformer that it must | ||
// not touch this advice | ||
// this is done to ensure that ClassLoaderInstrumentationModule.loadAdviceClassesEagerly works | ||
// correctly | ||
// we can therfore remove it, as soon as the AdviceTransformer is not applied anymore |
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.
[minor] maybe having a dedicated annotation for this could help, otherwise we just rely on a side-effect of having this ToReturned annotation present.
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 didn't want to introduce a separate mechanism here because this instrumentation is likely the very only case where this is needed and AdviceTransformer
will be a temporary thing anyway
...opentelemetry/javaagent/instrumentation/internal/classloader/DefineClassInstrumentation.java
Outdated
Show resolved
Hide resolved
...opentelemetry/javaagent/instrumentation/internal/classloader/DefineClassInstrumentation.java
Outdated
Show resolved
Hide resolved
if (justDelegateAdvice) { | ||
context.disableReturnTypeChange(); | ||
} |
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.
[for reviewer] without this the advice return type is assumed to be an array, which means skipOnIndex
is added when using skipOn
annotation attribute, and this breaks when advice is not inlined.
// Ensure that CallDepth is already loaded in case of bootstrapAdvice recursions with | ||
// ClassLoader.loadClass | ||
// This is required because CallDepth is a bootstrap class and therefore triggers our | ||
// ClassLoader.loadClass instrumentations | ||
Class.forName(CallDepth.class.getName()); |
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.
[minor] maybe add that this class needs to be explicitly initialized early because it's the one preventing recursive calls and is thus the one called first when loadClass
advice method is executed. Adding a comment where this first call is made would also be relevant too just in case someone tries to add something before it.
Also, could we make this static init part of the advice static init instead ? If possible that would be easier to link with the advice method code. On the other hand this CallDepth
init call might be needed elsewhere.
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 is in fact not the case that this class needs to be initialized early. It instead needs to be linked to IndyBootstrap
early:
When some IndyBootstrap.bootstrapAdvice
is executed for the first time, the JVM encounters a call to CallDepth.forClass
. When linking this call, the JVM itself invokes AgentClassLoader.loadClass(CallDepth)
to link that callsite. This in turn will delegate to the instrumented ClassLoader.loadClass
because it is a bootstrap class. It doesn't matter where within bootstrapAdvice
this call happens, it just happened by chance that it is the first one.
Also, could we make this static init part of the advice static init instead
For the reasons above: No, this is not possible, because it wouldn't link the usage of CallDepth
from within the IndyBootstrap
class.
...lemetry/javaagent/instrumentation/internal/classloader/LoadInjectedClassInstrumentation.java
Outdated
Show resolved
Hide resolved
ac1ae8a
to
f351043
Compare
...ntelemetry/javaagent/instrumentation/internal/classloader/BootDelegationInstrumentation.java
Outdated
Show resolved
Hide resolved
…ain/java/io/opentelemetry/javaagent/instrumentation/internal/classloader/BootDelegationInstrumentation.java Co-authored-by: SylvainJuge <763082+SylvainJuge@users.noreply.github.com>
...ntelemetry/javaagent/instrumentation/internal/classloader/BootDelegationInstrumentation.java
Outdated
Show resolved
Hide resolved
...lemetry/javaagent/instrumentation/internal/classloader/LoadInjectedClassInstrumentation.java
Outdated
Show resolved
Hide resolved
...o/opentelemetry/javaagent/tooling/instrumentation/indy/InstrumentationModuleClassLoader.java
Outdated
Show resolved
Hide resolved
@@ -52,9 +52,17 @@ public static DefineClassContext onEnter( | |||
classLoader, className, classBytes, offset, length); | |||
} | |||
|
|||
// TODO: the ToReturned does nothing except for signaling the AdviceTransformer that it must |
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.
Not related to this PR but I think we need to add a method to the experimental interface that lets instrumentation module declare that it is compatible with non-inline advice so we could disable the advice rewriting for it.
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 was thinking of it but felt like this instrumentation was a special case where this was needed and that would only be temporary until we remove support for non-indy instrumentations.
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.
Actually I was thinking that the main use for it would be to track the indy conversion progress and ensuring that modules that were made indy compatible once aren't broken by later changes. I'm pretty sure there was at least one more module that use the same trick to disable the advice rewriting.
This reverts commit 0154cd3.
...nt-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/IndyBootstrapDispatcher.java
Outdated
Show resolved
Hide resolved
javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/AgentClassLoader.java
Outdated
Show resolved
Hide resolved
try { | ||
clazz = Class.forName(name, false, this.getParent()); | ||
} catch (ClassNotFoundException e) { | ||
// ignore | ||
} | ||
} | ||
if (clazz == null) { | ||
clazz = super.findClass(name); |
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.
After changing the InstrumentationModuleClassLoader.defineClassWithPackage
this probably isn't necessary any more. Either revert or add a comment that this is here to avoid calling super.loadClass
.
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've also removed the loadClass
override in AgentClassLoader
. Remove it in InstrumentationModuleClassLoader
is not possible, because then the LoadClassAdvice
itself becomes recursive (not the bootstrapping this time):
[otel.javaagent 2024-10-24 12:42:55:462 +0200] [main] DEBUG io.opentelemetry.javaagent.tooling.instrumentation.indy.IndyBootstrap - Fixing nested instrumentation invokedynamic instruction bootstrapping for instrumented class java.lang.ClassLoader and advice io.opentelemetry.javaagent.instrumentation.internal.classloader.LoadInjectedClassInstrumentation$LoadClassAdvice.onEnter, the instrumentaiton should be active now
[otel.javaagent 2024-10-24 12:42:55:463 +0200] [otel-javaagent-transform-safe-logger] DEBUG io.opentelemetry.javaagent.tooling.AgentInstaller$TransformLoggingListener - Failed to handle io.opentelemetry.javaagent.ClassLoaderData$$29539e36 for transformation on class loader io.opentelemetry.javaagent.tooling.util.ClassLoaderMap$1@29539e36
java.lang.StackOverflowError
at io.opentelemetry.javaagent.instrumentation.internal.classloader.LoadInjectedClassInstrumentation$LoadClassAdvice.onEnter(LoadInjectedClassInstrumentation.java:59)
at java.lang.ClassLoader.loadClass(ClassLoader.java:352)
at io.opentelemetry.javaagent.instrumentation.internal.classloader.LoadInjectedClassInstrumentation$LoadClassAdvice.onEnter(LoadInjectedClassInstrumentation.java:59)
at java.lang.ClassLoader.loadClass(ClassLoader.java:352)
at io.opentelemetry.javaagent.instrumentation.internal.classloader.LoadInjectedClassInstrumentation$LoadClassAdvice.onEnter(LoadInjectedClassInstrumentation.java:59)
...
at java.lang.ClassLoader.loadClass(ClassLoader.java:352)
at io.opentelemetry.javaagent.instrumentation.internal.classloader.LoadInjectedClassInstrumentation$LoadClassAdvice.onEnter(LoadInjectedClassInstrumentation.java:59)
at java.lang.ClassLoader.loadClass(ClassLoader.java:352)
at io.opentelemetry.javaagent.instrumentation.internal.classloader.LoadInjectedClassInstrumentation$LoadClassAdvice.onEnter(LoadInjectedClassInstrumentation.java:59)
at java.lang.ClassLoader.loadClass(ClassLoader.java:352)
at io.opentelemetry.javaagent.tooling.ignore.IgnoredClassLoadersMatcher.loadsExpectedClass(IgnoredClassLoadersMatcher.java:78)
at io.opentelemetry.javaagent.tooling.ignore.IgnoredClassLoadersMatcher.delegatesToBootstrap(IgnoredClassLoadersMatcher.java:69)
If you prefer to, we could also use CallDepth
in the LoadClassAdvice
instead I think.
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 think it is fine as it is for now.
...o/opentelemetry/javaagent/tooling/instrumentation/indy/InstrumentationModuleClassLoader.java
Outdated
Show resolved
Hide resolved
…ling/instrumentation/indy/InstrumentationModuleClassLoader.java
These instrumentations were quite some fun ones.
They are slightly problematic, because they instrument
ClassLoader.loadClass
andClassLoader.defineClass
.This is difficult with invokedynamic, because the bootstrapping of the
invokedynamic
instructions inserted for these instrumentations itself involves loading and defining of classes, causing an infinite recursion.Here is how this PR avoid these recursions:
Avoiding ClassLoader.loadClass recursions
When an Advice is bootstrapped,
ClassLoader.loadClass
is invoked for loading the Advice-class and for initializing the dependencies ofIndyBootsrap
in case this is the very first bootstrapped advice. We ahve avoided this by:AgentClassLoader
andInstrumentationModuleClassloader
from instrumentations instrumentingClassLoader.loadClass
overriddesAgentClassLoader
andInstrumentationModuleClassloader
override all overloads ofClassLoader.loadClass
The latter was needed because otherwise we would still get a recursion if invoking and overload, because it would fallback to the instrumented super method.
This is almost good enough, because it avoid the recursion for all classes loaded by the excluded loaders themselves. However, for classes bootstrap-classloader classes (such as
CallDepth
) used byIndyBootstrap.bootstrap
we still would get a recursion, because thenAgentClassLoader
delegates tosuper.loadClass
. This could occur on the very first invocation toIndyBootstrap.bootstrap
if those classes are not linked yet. We avoid this by linking the corresponding classes (justCallDepth
at the moment) eagerly in the static initializer ofIndyBootstrap
.Avoiding ClassLoader.defineClass recursions
When an Advice is bootstrapped, the target Advice-class is loaded for the first time and therefore
ClassLoader.defineClass
is invoked for it. This therefore by defintion yields a invokedynamic bootstrapping recursion for theDefineClassInstrumentation
.We avoid this by adding a new mechanism to allow Advice-classes to not be loaded on the first invocation of the instrumented method, but instead already loading them when the
invokedynamic
bytecode is inserted. This already prevents the recursion, because then the loading of the Advice class during bootstrapping will not involve adefineClass
call anymore.