-
Notifications
You must be signed in to change notification settings - Fork 1k
Make internal classloader instrumentation indy compatible #12242
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
Changes from 28 commits
0fd0e09
d28efde
18b9029
87f7d42
116daaa
106904e
f351043
e8a5ef2
67b1d40
57d73cd
b46acad
e54d881
393e3ef
cd7c0f9
7fc8277
33b6130
2456716
dc26c83
0154cd3
5f3f166
b01f387
712c70d
85beb7a
3e03153
05bc99f
492c968
6662850
1173824
43a4ae0
03932e4
add664a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -142,6 +142,15 @@ private static int getJavaVersion() { | |
| return Integer.parseInt(javaSpecVersion); | ||
| } | ||
|
|
||
| @Override | ||
| public Class<?> loadClass(String name) throws ClassNotFoundException { | ||
| // We explicitly override loadClass from ClassLoader to ensure | ||
| // that loadClass is properly excluded from our internal ClassLoader Instrumentations | ||
| // (e.g. LoadInjectedClassInstrumentation, BooDelegationInstrumentation) | ||
| // Otherwise this will cause recursion in invokedynamic linkage | ||
| return loadClass(name, false); | ||
| } | ||
|
|
||
| @Override | ||
| public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { | ||
| // ContextStorageOverride is meant for library instrumentation we don't want it to apply to our | ||
|
|
@@ -158,7 +167,14 @@ public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundExce | |
| } | ||
| // search from parent and urls added to this loader | ||
| if (clazz == null) { | ||
| clazz = super.loadClass(name, false); | ||
| try { | ||
JonasKunz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| clazz = Class.forName(name, false, this.getParent()); | ||
| } catch (ClassNotFoundException e) { | ||
| // ignore | ||
| } | ||
| } | ||
| if (clazz == null) { | ||
| clazz = super.findClass(name); | ||
|
||
| } | ||
| if (resolve) { | ||
| resolveClass(clazz); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.javaagent.tooling.instrumentation.indy; | ||
|
|
||
| import java.lang.invoke.MutableCallSite; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
| import java.util.function.Function; | ||
| import java.util.function.Supplier; | ||
| import javax.annotation.Nullable; | ||
|
|
||
| class AdviceBootstrapState implements AutoCloseable { | ||
|
|
||
| private static final ThreadLocal<Map<Key, AdviceBootstrapState>> stateForCurrentThread = | ||
| ThreadLocal.withInitial(HashMap::new); | ||
|
|
||
| private final Key key; | ||
| private int recursionDepth; | ||
| @Nullable private MutableCallSite nestedCallSite; | ||
|
|
||
| /** | ||
| * We have to eagerly initialize to not cause a lambda construction during {@link #enter(Class, | ||
| * String, String, String, String)}. | ||
| */ | ||
| private static final Function<Key, AdviceBootstrapState> CONSTRUCTOR = AdviceBootstrapState::new; | ||
|
|
||
| private AdviceBootstrapState(Key key) { | ||
| this.key = key; | ||
| // enter will increment it by one, so 0 is the value for non-recursive calls | ||
| recursionDepth = -1; | ||
| } | ||
|
|
||
| static void initialize() { | ||
| // Eager initialize everything because we could run into recursions doing this during advice | ||
| // bootstrapping | ||
| stateForCurrentThread.get(); | ||
| stateForCurrentThread.remove(); | ||
| try { | ||
| Class.forName(Key.class.getName()); | ||
| } catch (ClassNotFoundException e) { | ||
| throw new IllegalStateException(e); | ||
| } | ||
| } | ||
|
|
||
| static AdviceBootstrapState enter( | ||
| Class<?> instrumentedClass, | ||
| String moduleClassName, | ||
| String adviceClassName, | ||
| String adviceMethodName, | ||
| String adviceMethodDescriptor) { | ||
| Key key = | ||
| new Key( | ||
| instrumentedClass, | ||
| moduleClassName, | ||
| adviceClassName, | ||
| adviceMethodName, | ||
| adviceMethodDescriptor); | ||
| AdviceBootstrapState state = stateForCurrentThread.get().computeIfAbsent(key, CONSTRUCTOR); | ||
| state.recursionDepth++; | ||
| return state; | ||
| } | ||
|
|
||
| public boolean isNestedInvocation() { | ||
| return recursionDepth > 0; | ||
| } | ||
|
|
||
| public MutableCallSite getOrInitMutableCallSite(Supplier<MutableCallSite> initializer) { | ||
| if (nestedCallSite == null) { | ||
| nestedCallSite = initializer.get(); | ||
| } | ||
| return nestedCallSite; | ||
| } | ||
|
|
||
| public void initMutableCallSite(MutableCallSite mutableCallSite) { | ||
| if (nestedCallSite != null) { | ||
| throw new IllegalStateException("callsite has already been initialized"); | ||
| } | ||
| nestedCallSite = mutableCallSite; | ||
| } | ||
|
|
||
| @Nullable | ||
| public MutableCallSite getMutableCallSite() { | ||
| return nestedCallSite; | ||
| } | ||
|
|
||
| @Override | ||
| public void close() { | ||
| if (recursionDepth == 0) { | ||
| Map<Key, AdviceBootstrapState> stateMap = stateForCurrentThread.get(); | ||
| stateMap.remove(key); | ||
| if (stateMap.isEmpty()) { | ||
| // Do not leave an empty map dangling as thread local | ||
| stateForCurrentThread.remove(); | ||
| } | ||
| } else { | ||
| recursionDepth--; | ||
| } | ||
| } | ||
|
|
||
| /** Key uniquely identifying a single invokedynamic instruction inserted for an advice */ | ||
| private static class Key { | ||
|
|
||
| private final Class<?> instrumentedClass; | ||
| private final String moduleClassName; | ||
| private final String adviceClassName; | ||
| private final String adviceMethodName; | ||
| private final String adviceMethodDescriptor; | ||
|
|
||
| private Key( | ||
| Class<?> instrumentedClass, | ||
| String moduleClassName, | ||
| String adviceClassName, | ||
| String adviceMethodName, | ||
| String adviceMethodDescriptor) { | ||
| this.instrumentedClass = instrumentedClass; | ||
| this.moduleClassName = moduleClassName; | ||
| this.adviceClassName = adviceClassName; | ||
| this.adviceMethodName = adviceMethodName; | ||
| this.adviceMethodDescriptor = adviceMethodDescriptor; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object o) { | ||
| if (this == o) { | ||
| return true; | ||
| } | ||
| if (o == null || !(o instanceof Key)) { | ||
| return false; | ||
| } | ||
|
|
||
| Key that = (Key) o; | ||
| return instrumentedClass.equals(that.instrumentedClass) | ||
| && moduleClassName.equals(that.moduleClassName) | ||
| && adviceClassName.equals(that.adviceClassName) | ||
| && adviceMethodName.equals(that.adviceMethodName) | ||
| && adviceMethodDescriptor.equals(that.adviceMethodDescriptor); | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| int result = instrumentedClass.hashCode(); | ||
| result = 31 * result + moduleClassName.hashCode(); | ||
| result = 31 * result + adviceClassName.hashCode(); | ||
| result = 31 * result + adviceMethodName.hashCode(); | ||
| result = 31 * result + adviceMethodDescriptor.hashCode(); | ||
| return result; | ||
| } | ||
| } | ||
| } |
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.