Skip to content

Commit 7b49d12

Browse files
serkan-ozallaurit
andauthored
Delay loading InetAddressResolverProvider until after the agent has started (#11987)
Co-authored-by: Lauri Tulmin <[email protected]>
1 parent 7d004b5 commit 7b49d12

File tree

9 files changed

+216
-0
lines changed

9 files changed

+216
-0
lines changed

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

+18
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public final class AgentInitializer {
2626
@Nullable private static ClassLoader agentClassLoader = null;
2727
@Nullable private static AgentStarter agentStarter = null;
2828
private static boolean isSecurityManagerSupportEnabled = false;
29+
private static volatile boolean agentStarted = false;
2930

3031
public static void initialize(Instrumentation inst, File javaagentFile, boolean fromPremain)
3132
throws Exception {
@@ -51,6 +52,7 @@ public Void run() throws Exception {
5152
agentStarter = createAgentStarter(agentClassLoader, inst, javaagentFile);
5253
if (!fromPremain || !delayAgentStart()) {
5354
agentStarter.start();
55+
agentStarted = true;
5456
}
5557
return null;
5658
}
@@ -149,11 +151,27 @@ public static void delayedStartHook() throws Exception {
149151
@Override
150152
public Void run() {
151153
agentStarter.start();
154+
agentStarted = true;
152155
return null;
153156
}
154157
});
155158
}
156159

160+
/**
161+
* Check whether agent has started or not along with VM.
162+
*
163+
* <p>This method is used by
164+
* io.opentelemetry.javaagent.tooling.AgentStarterImpl#InetAddressClassFileTransformer internally
165+
* to check whether agent has started.
166+
*
167+
* @param vmStarted flag about whether VM has started or not.
168+
* @return {@code true} if agent has started or not along with VM, {@code false} otherwise.
169+
*/
170+
@SuppressWarnings("unused")
171+
public static boolean isAgentStarted(boolean vmStarted) {
172+
return vmStarted && agentStarted;
173+
}
174+
157175
public static ClassLoader getExtensionsClassLoader() {
158176
// agentStarter can be null when running tests
159177
return agentStarter != null ? agentStarter.getExtensionClassLoader() : null;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
plugins {
2+
id("otel.javaagent-testing")
3+
}
4+
5+
dependencies {
6+
compileOnly("io.opentelemetry:opentelemetry-sdk-common")
7+
}
8+
9+
otelJava {
10+
minJavaVersionSupported.set(JavaVersion.VERSION_18)
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package testing;
7+
8+
import com.google.auto.service.AutoService;
9+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
10+
import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider;
11+
import io.opentelemetry.sdk.resources.Resource;
12+
import java.net.InetAddress;
13+
import java.net.UnknownHostException;
14+
15+
@AutoService(ResourceProvider.class)
16+
public class TestResourceProvider implements ResourceProvider {
17+
18+
@Override
19+
public Resource createResource(ConfigProperties config) {
20+
// used in test to determine whether this method was called
21+
System.setProperty("test.resource.provider.called", "true");
22+
// this call trigger loading InetAddressResolverProvider SPI on jdk 18
23+
try {
24+
InetAddress.getLocalHost();
25+
} catch (UnknownHostException e) {
26+
throw new IllegalStateException(e);
27+
}
28+
return Resource.empty();
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.tooling.inetaddress;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
10+
import java.net.InetAddress;
11+
import org.junit.jupiter.api.Test;
12+
13+
public class InetAddressResolverTest {
14+
15+
@Test
16+
void agentStartShouldNotTriggerLoadingCustomInetAddressResolvers() throws Exception {
17+
// This system property is set in TestResourceProvider
18+
assertThat(System.getProperty("test.resource.provider.called")).isEqualTo("true");
19+
// Agent start should not trigger loading (and instantiating) custom InetAddress resolvers
20+
assertThat(TestAddressResolver.isInstantiated()).isFalse();
21+
22+
// Trigger loading (and instantiating) custom InetAddress resolvers manually
23+
InetAddress.getAllByName("test");
24+
25+
// Verify that custom InetAddress resolver loaded and instantiated
26+
assertThat(TestAddressResolver.isInstantiated()).isTrue();
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.tooling.inetaddress;
7+
8+
import java.net.InetAddress;
9+
import java.net.UnknownHostException;
10+
import java.net.spi.InetAddressResolver;
11+
import java.util.stream.Stream;
12+
13+
public class TestAddressResolver implements InetAddressResolver {
14+
15+
private static volatile boolean instantiated = false;
16+
17+
@SuppressWarnings("StaticAssignmentInConstructor")
18+
public TestAddressResolver() {
19+
TestAddressResolver.instantiated = true;
20+
}
21+
22+
public static boolean isInstantiated() {
23+
return instantiated;
24+
}
25+
26+
@Override
27+
public Stream<InetAddress> lookupByName(String host, LookupPolicy lookupPolicy)
28+
throws UnknownHostException {
29+
if (host.equals("test")) {
30+
return Stream.of(InetAddress.getByAddress(new byte[] {127, 0, 0, 1}));
31+
}
32+
throw new UnknownHostException();
33+
}
34+
35+
@Override
36+
public String lookupByAddress(byte[] addr) {
37+
throw new UnsupportedOperationException();
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.tooling.inetaddress;
7+
8+
import java.net.spi.InetAddressResolver;
9+
import java.net.spi.InetAddressResolverProvider;
10+
11+
public class TestAddressResolverProvider extends InetAddressResolverProvider {
12+
13+
@Override
14+
public InetAddressResolver get(Configuration configuration) {
15+
return new TestAddressResolver();
16+
}
17+
18+
@Override
19+
public String name() {
20+
return "Test Internet Address Resolver Provider";
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
io.opentelemetry.javaagent.tooling.inetaddress.TestAddressResolverProvider

javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentStarterImpl.java

+66
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ public boolean delayStart() {
6868

6969
@Override
7070
public void start() {
71+
installTransformers();
72+
7173
EarlyInitAgentConfig earlyConfig = EarlyInitAgentConfig.create();
7274
extensionClassLoader = createExtensionClassLoader(getClass().getClassLoader(), earlyConfig);
7375

@@ -115,6 +117,14 @@ public void start() {
115117
}
116118
}
117119

120+
private void installTransformers() {
121+
// prevents loading InetAddressResolverProvider SPI before agent has started
122+
// https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/7130
123+
// https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/10921
124+
InetAddressClassFileTransformer transformer = new InetAddressClassFileTransformer();
125+
instrumentation.addTransformer(transformer, true);
126+
}
127+
118128
@SuppressWarnings("SystemOut")
119129
private static void logUnrecognizedLoggerImplWarning(String loggerImplementationName) {
120130
System.err.println(
@@ -180,4 +190,60 @@ public void visitCode() {
180190
return hookInserted ? cw.toByteArray() : null;
181191
}
182192
}
193+
194+
private static class InetAddressClassFileTransformer implements ClassFileTransformer {
195+
boolean hookInserted = false;
196+
197+
@Override
198+
public byte[] transform(
199+
ClassLoader loader,
200+
String className,
201+
Class<?> classBeingRedefined,
202+
ProtectionDomain protectionDomain,
203+
byte[] classfileBuffer) {
204+
if (!"java/net/InetAddress".equals(className)) {
205+
return null;
206+
}
207+
ClassReader cr = new ClassReader(classfileBuffer);
208+
ClassWriter cw = new ClassWriter(cr, 0);
209+
ClassVisitor cv =
210+
new ClassVisitor(AsmApi.VERSION, cw) {
211+
@Override
212+
public MethodVisitor visitMethod(
213+
int access, String name, String descriptor, String signature, String[] exceptions) {
214+
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
215+
if (!"resolver".equals(name)) {
216+
return mv;
217+
}
218+
return new MethodVisitor(api, mv) {
219+
@Override
220+
public void visitMethodInsn(
221+
int opcode,
222+
String ownerClassName,
223+
String methodName,
224+
String descriptor,
225+
boolean isInterface) {
226+
super.visitMethodInsn(
227+
opcode, ownerClassName, methodName, descriptor, isInterface);
228+
// rewrite Vm.isBooted() to AgentInitializer.isAgentStarted(Vm.isBooted())
229+
if ("jdk/internal/misc/VM".equals(ownerClassName)
230+
&& "isBooted".equals(methodName)) {
231+
super.visitMethodInsn(
232+
Opcodes.INVOKESTATIC,
233+
Type.getInternalName(AgentInitializer.class),
234+
"isAgentStarted",
235+
"(Z)Z",
236+
false);
237+
hookInserted = true;
238+
}
239+
}
240+
};
241+
}
242+
};
243+
244+
cr.accept(cv, 0);
245+
246+
return hookInserted ? cw.toByteArray() : null;
247+
}
248+
}
183249
}

settings.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ include(":javaagent-bootstrap")
121121
include(":javaagent-extension-api")
122122
include(":javaagent-tooling")
123123
include(":javaagent-tooling:javaagent-tooling-java9")
124+
include(":javaagent-tooling:jdk18-testing")
124125
include(":javaagent-internal-logging-application")
125126
include(":javaagent-internal-logging-simple")
126127
include(":javaagent")

0 commit comments

Comments
 (0)