Skip to content

Commit dc26c83

Browse files
committed
Added support for nested invokedynamic bootstrapping using MutableCallSite
1 parent 2456716 commit dc26c83

File tree

3 files changed

+161
-26
lines changed

3 files changed

+161
-26
lines changed

javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/IndyBootstrapDispatcher.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public static CallSite bootstrap(
5555
}
5656

5757
// package visibility for testing
58-
static MethodHandle generateNoopMethodHandle(MethodType methodType) {
58+
public static MethodHandle generateNoopMethodHandle(MethodType methodType) {
5959
Class<?> returnType = methodType.returnType();
6060
MethodHandle noopNoArg;
6161
if (returnType == void.class) {
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.tooling.instrumentation.indy;
7+
8+
import java.lang.invoke.MutableCallSite;
9+
import java.util.HashMap;
10+
import java.util.Map;
11+
import java.util.function.Supplier;
12+
import javax.annotation.Nullable;
13+
14+
class AdviceBootstrapState implements AutoCloseable {
15+
16+
private static final ThreadLocal<Map<Key, AdviceBootstrapState>> stateForCurrentThread =
17+
ThreadLocal.withInitial(HashMap::new);
18+
19+
private final Key key;
20+
private int recursionDepth;
21+
@Nullable private MutableCallSite nestedCallSite;
22+
23+
private AdviceBootstrapState(Key key) {
24+
this.key = key;
25+
// enter will increment it by one, so 0 is the value for non-recursive calls
26+
recursionDepth = -1;
27+
}
28+
29+
public static AdviceBootstrapState enter(
30+
Class<?> instrumentedClass,
31+
String moduleClassName,
32+
String adviceClassName,
33+
String adviceMethodName,
34+
String adviceMethodDescriptor) {
35+
Key key =
36+
new Key(
37+
instrumentedClass,
38+
moduleClassName,
39+
adviceClassName,
40+
adviceMethodName,
41+
adviceMethodDescriptor);
42+
AdviceBootstrapState state =
43+
stateForCurrentThread.get().computeIfAbsent(key, k -> new AdviceBootstrapState(key));
44+
state.recursionDepth++;
45+
return state;
46+
}
47+
48+
public boolean isNestedInvocation() {
49+
return recursionDepth > 0;
50+
}
51+
52+
public MutableCallSite getOrInitMutableCallSite(Supplier<MutableCallSite> initializer) {
53+
if (nestedCallSite == null) {
54+
nestedCallSite = initializer.get();
55+
}
56+
return nestedCallSite;
57+
}
58+
59+
@Nullable
60+
public MutableCallSite getMutableCallSite() {
61+
return nestedCallSite;
62+
}
63+
64+
@Override
65+
public void close() {
66+
if (recursionDepth == 0) {
67+
Map<Key, AdviceBootstrapState> stateMap = stateForCurrentThread.get();
68+
stateMap.remove(key);
69+
if (stateMap.isEmpty()) {
70+
// Do not leave an empty map dangling as thread local
71+
stateForCurrentThread.remove();
72+
}
73+
} else {
74+
recursionDepth--;
75+
}
76+
}
77+
78+
/** Key uniquely identifying a single invokedynamic instruction inserted for an advice */
79+
private static class Key {
80+
81+
private final Class<?> instrumentedClass;
82+
private final String moduleClassName;
83+
private final String adviceClassName;
84+
private final String adviceMethodName;
85+
private final String adviceMethodDescriptor;
86+
87+
private Key(
88+
Class<?> instrumentedClass,
89+
String moduleClassName,
90+
String adviceClassName,
91+
String adviceMethodName,
92+
String adviceMethodDescriptor) {
93+
this.instrumentedClass = instrumentedClass;
94+
this.moduleClassName = moduleClassName;
95+
this.adviceClassName = adviceClassName;
96+
this.adviceMethodName = adviceMethodName;
97+
this.adviceMethodDescriptor = adviceMethodDescriptor;
98+
}
99+
100+
@Override
101+
public boolean equals(Object o) {
102+
if (this == o) return true;
103+
if (o == null || getClass() != o.getClass()) return false;
104+
105+
Key that = (Key) o;
106+
return instrumentedClass.equals(that.instrumentedClass)
107+
&& moduleClassName.equals(that.moduleClassName)
108+
&& adviceClassName.equals(that.adviceClassName)
109+
&& adviceMethodName.equals(that.adviceMethodName)
110+
&& adviceMethodDescriptor.equals(that.adviceMethodDescriptor);
111+
}
112+
113+
@Override
114+
public int hashCode() {
115+
int result = instrumentedClass.hashCode();
116+
result = 31 * result + moduleClassName.hashCode();
117+
result = 31 * result + adviceClassName.hashCode();
118+
result = 31 * result + adviceMethodName.hashCode();
119+
result = 31 * result + adviceMethodDescriptor.hashCode();
120+
return result;
121+
}
122+
}
123+
}

javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/IndyBootstrap.java

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55

66
package io.opentelemetry.javaagent.tooling.instrumentation.indy;
77

8-
import io.opentelemetry.javaagent.bootstrap.CallDepth;
98
import io.opentelemetry.javaagent.bootstrap.IndyBootstrapDispatcher;
109
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
1110
import java.lang.invoke.CallSite;
1211
import java.lang.invoke.ConstantCallSite;
1312
import java.lang.invoke.MethodHandle;
1413
import java.lang.invoke.MethodHandles;
1514
import java.lang.invoke.MethodType;
15+
import java.lang.invoke.MutableCallSite;
1616
import java.lang.reflect.Method;
1717
import java.security.PrivilegedAction;
1818
import java.util.Arrays;
@@ -84,20 +84,14 @@ public class IndyBootstrap {
8484

8585
MethodType bootstrapMethodType =
8686
MethodType.methodType(
87-
ConstantCallSite.class,
87+
CallSite.class,
8888
MethodHandles.Lookup.class,
8989
String.class,
9090
MethodType.class,
9191
Object[].class);
9292

9393
IndyBootstrapDispatcher.init(
9494
MethodHandles.lookup().findStatic(IndyBootstrap.class, "bootstrap", bootstrapMethodType));
95-
96-
// Ensure that CallDepth is already loaded in case of bootstrapAdvice recursions with
97-
// ClassLoader.loadClass
98-
// This is required because CallDepth is a bootstrap class and therefore triggers our
99-
// ClassLoader.loadClass instrumentations
100-
Class.forName(CallDepth.class.getName());
10195
} catch (Exception e) {
10296
throw new IllegalStateException(e);
10397
}
@@ -111,7 +105,7 @@ public static Method getIndyBootstrapMethod() {
111105

112106
@Nullable
113107
@SuppressWarnings({"unused", "removal"}) // SecurityManager and AccessController are deprecated
114-
private static ConstantCallSite bootstrap(
108+
private static CallSite bootstrap(
115109
MethodHandles.Lookup lookup,
116110
String adviceMethodName,
117111
MethodType adviceMethodType,
@@ -124,11 +118,11 @@ private static ConstantCallSite bootstrap(
124118
// callsite resolution needs privileged access to call Class#getClassLoader() and
125119
// MethodHandles$Lookup#findStatic
126120
return java.security.AccessController.doPrivileged(
127-
(PrivilegedAction<ConstantCallSite>)
121+
(PrivilegedAction<CallSite>)
128122
() -> internalBootstrap(lookup, adviceMethodName, adviceMethodType, args));
129123
}
130124

131-
private static ConstantCallSite internalBootstrap(
125+
private static CallSite internalBootstrap(
132126
MethodHandles.Lookup lookup,
133127
String adviceMethodName,
134128
MethodType adviceMethodType,
@@ -163,29 +157,39 @@ private static ConstantCallSite internalBootstrap(
163157
}
164158
}
165159

166-
private static ConstantCallSite bootstrapAdvice(
160+
private static CallSite bootstrapAdvice(
167161
MethodHandles.Lookup lookup,
168162
String adviceMethodName,
169163
MethodType invokedynamicMethodType,
170164
String moduleClassName,
171165
String adviceMethodDescriptor,
172166
String adviceClassName)
173167
throws NoSuchMethodException, IllegalAccessException, ClassNotFoundException {
174-
CallDepth callDepth = CallDepth.forClass(IndyBootstrap.class);
175-
try {
176-
if (callDepth.getAndIncrement() > 0) {
168+
try (AdviceBootstrapState nestedState =
169+
AdviceBootstrapState.enter(
170+
lookup.lookupClass(),
171+
moduleClassName,
172+
adviceClassName,
173+
adviceMethodName,
174+
adviceMethodDescriptor)) {
175+
if (nestedState.isNestedInvocation()) {
177176
// avoid re-entrancy and stack overflow errors, which may happen when bootstrapping an
178177
// instrumentation that also gets triggered during the bootstrap
179178
// for example, adding correlation ids to the thread context when executing logger.debug.
180179
logger.log(
181-
Level.SEVERE,
182-
"Nested instrumented invokedynamic instruction linkage detected for instrumented class {0} and advice {1}.{2}, the instrumentation might not work correctly",
180+
Level.FINE,
181+
"Nested instrumented invokedynamic instruction linkage detected for instrumented class {0} and advice {1}.{2}, this invocation won't be instrumented",
183182
new Object[] {lookup.lookupClass().getName(), adviceClassName, adviceMethodName});
184-
logger.log(
185-
Level.SEVERE,
186-
"Stacktrace for nested invokedynamic instruction linkage:",
187-
new Throwable());
188-
return null;
183+
if (logger.isLoggable(Level.FINEST)) {
184+
logger.log(
185+
Level.FINEST,
186+
"Stacktrace for nested invokedynamic instruction linkage:",
187+
new Throwable());
188+
}
189+
return nestedState.getOrInitMutableCallSite(
190+
() ->
191+
new MutableCallSite(
192+
IndyBootstrapDispatcher.generateNoopMethodHandle(invokedynamicMethodType)));
189193
}
190194

191195
InstrumentationModuleClassLoader instrumentationClassloader =
@@ -203,9 +207,17 @@ private static ConstantCallSite bootstrapAdvice(
203207
.getLookup()
204208
.findStatic(adviceClass, adviceMethodName, actualAdviceMethodType)
205209
.asType(invokedynamicMethodType);
206-
return new ConstantCallSite(methodHandle);
207-
} finally {
208-
callDepth.decrementAndGet();
210+
211+
MutableCallSite nestedBootstrapCallSite = nestedState.getMutableCallSite();
212+
if (nestedBootstrapCallSite != null) {
213+
// There have been nested bootstrapping attempts
214+
// Update the callsite of those to run the actual instrumentation
215+
nestedBootstrapCallSite.setTarget(methodHandle);
216+
MutableCallSite.syncAll(new MutableCallSite[] {nestedBootstrapCallSite});
217+
return nestedBootstrapCallSite;
218+
} else {
219+
return new ConstantCallSite(methodHandle);
220+
}
209221
}
210222
}
211223

0 commit comments

Comments
 (0)