Fix re-transform bug when enhanced class proxy parent method (#659)

This commit is contained in:
Gong Dewei 2023-12-23 00:01:30 +08:00 committed by GitHub
parent 7aec68263b
commit 4f2f81e181
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 268 additions and 36 deletions

View File

@ -8,7 +8,7 @@ Release Notes.
* Fix NoSuchMethodError in mvc-annotation-commons and change deprecated method.
* fix forkjoinpool plugin in JDK11。
* Support for tracing spring-cloud-gateway 4.x in gateway-4.x-plugin.
* Fix re-transform bug when plugin enhanced class proxy parent method.
#### Documentation

View File

@ -32,12 +32,14 @@ import net.bytebuddy.agent.builder.SWNativeMethodStrategy;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.scaffold.MethodGraph;
import net.bytebuddy.dynamic.scaffold.TypeValidation;
import org.apache.skywalking.apm.agent.bytebuddy.SWAuxiliaryTypeNamingStrategy;
import net.bytebuddy.implementation.SWImplementationContextFactory;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;
import org.apache.skywalking.apm.agent.bytebuddy.SWMethodGraphCompilerDelegate;
import org.apache.skywalking.apm.agent.bytebuddy.SWMethodNameTransformer;
import org.apache.skywalking.apm.agent.core.boot.AgentPackageNotFoundException;
import org.apache.skywalking.apm.agent.core.boot.ServiceManager;
@ -99,6 +101,23 @@ public class SkyWalkingAgent {
return;
}
try {
installClassTransformer(instrumentation, pluginFinder);
} catch (Exception e) {
LOGGER.error(e, "Skywalking agent installed class transformer failure.");
}
try {
ServiceManager.INSTANCE.boot();
} catch (Exception e) {
LOGGER.error(e, "Skywalking agent boot failure.");
}
Runtime.getRuntime()
.addShutdownHook(new Thread(ServiceManager.INSTANCE::shutdown, "skywalking service shutdown thread"));
}
static void installClassTransformer(Instrumentation instrumentation, PluginFinder pluginFinder) throws Exception {
LOGGER.info("Skywalking agent begin to install transformer ...");
AgentBuilder agentBuilder = newAgentBuilder().ignore(
@ -116,15 +135,13 @@ public class SkyWalkingAgent {
try {
agentBuilder = BootstrapInstrumentBoost.inject(pluginFinder, instrumentation, agentBuilder, edgeClasses);
} catch (Exception e) {
LOGGER.error(e, "SkyWalking agent inject bootstrap instrumentation failure. Shutting down.");
return;
throw new Exception("SkyWalking agent inject bootstrap instrumentation failure. Shutting down.", e);
}
try {
agentBuilder = JDK9ModuleExporter.openReadEdge(instrumentation, agentBuilder, edgeClasses);
} catch (Exception e) {
LOGGER.error(e, "SkyWalking agent open read edge in JDK 9+ failure. Shutting down.");
return;
throw new Exception("SkyWalking agent open read edge in JDK 9+ failure. Shutting down.", e);
}
agentBuilder.type(pluginFinder.buildMatch())
@ -137,15 +154,6 @@ public class SkyWalkingAgent {
PluginFinder.pluginInitCompleted();
LOGGER.info("Skywalking agent transformer has installed.");
try {
ServiceManager.INSTANCE.boot();
} catch (Exception e) {
LOGGER.error(e, "Skywalking agent boot failure.");
}
Runtime.getRuntime()
.addShutdownHook(new Thread(ServiceManager.INSTANCE::shutdown, "skywalking service shutdown thread"));
}
/**
@ -156,7 +164,8 @@ public class SkyWalkingAgent {
final ByteBuddy byteBuddy = new ByteBuddy()
.with(TypeValidation.of(Config.Agent.IS_OPEN_DEBUGGING_CLASS))
.with(new SWAuxiliaryTypeNamingStrategy(NAME_TRAIT))
.with(new SWImplementationContextFactory(NAME_TRAIT));
.with(new SWImplementationContextFactory(NAME_TRAIT))
.with(new SWMethodGraphCompilerDelegate(MethodGraph.Compiler.DEFAULT));
return new SWAgentBuilderDefault(byteBuddy, new SWNativeMethodStrategy(NAME_TRAIT))
.with(new SWDescriptionStrategy(NAME_TRAIT));

View File

@ -42,5 +42,11 @@
<version>${jedis.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-agent</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,83 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.skywalking.apm.agent;
import com.google.common.base.Stopwatch;
import net.bytebuddy.agent.ByteBuddyAgent;
import org.apache.skywalking.apm.agent.core.logging.core.SystemOutWriter;
import org.apache.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine;
import org.apache.skywalking.apm.agent.core.plugin.ByteBuddyCoreClasses;
import org.apache.skywalking.apm.agent.core.plugin.PluginFinder;
import org.apache.skywalking.apm.plugin.jedis.v3.define.JedisInstrumentation;
import org.junit.Assert;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import java.lang.instrument.Instrumentation;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class JedisInstrumentationTest {
@Test
public void test() throws Exception {
// tested plugins
List<AbstractClassEnhancePluginDefine> plugins = Arrays.asList(new JedisInstrumentation());
// remove shade prefix
String[] classes = ByteBuddyCoreClasses.CLASSES;
for (int i = 0; i < classes.length; i++) {
classes[i] = classes[i].replaceFirst("org.apache.skywalking.apm.dependencies.", "");
}
Instrumentation instrumentation = ByteBuddyAgent.install();
SkyWalkingAgent.installClassTransformer(instrumentation, new PluginFinder(plugins));
// first load
Jedis jedis = new Jedis();
try {
jedis.get("mykey");
} catch (Exception e) {
Assert.assertTrue(e.toString(), e.toString().contains("JedisConnectionException"));
}
log("Do re-transform class : redis.clients.jedis.Jedis ..");
Stopwatch stopwatch = Stopwatch.createStarted();
// re-transform class
for (int i = 0; i < 4; i++) {
stopwatch.reset();
stopwatch.start();
instrumentation.retransformClasses(Jedis.class);
long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
log("Re-transform class cost: " + elapsed);
}
// test after re-transform class
try {
jedis.get("mykey");
} catch (Exception e) {
Assert.assertTrue(e.toString(), e.toString().contains("JedisConnectionException"));
}
}
private void log(String message) {
SystemOutWriter.INSTANCE.write(message);
}
}

View File

@ -29,7 +29,6 @@ import net.bytebuddy.implementation.bytecode.StackSize;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.utility.JavaModule;
import net.bytebuddy.utility.nullability.MaybeNull;
import org.apache.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
import java.io.Serializable;
@ -38,6 +37,8 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
@ -109,7 +110,7 @@ public class SWDescriptionStrategy implements AgentBuilder.DescriptionStrategy {
}
}
// wrap result
return new SWTypeDescriptionWrapper(delegate.apply(name, type, typePool, circularityLock, classLoader, module), nameTrait);
return new SWTypeDescriptionWrapper(delegate.apply(name, type, typePool, circularityLock, classLoader, module), nameTrait, classLoader, name);
}
/**
@ -122,25 +123,40 @@ public class SWDescriptionStrategy implements AgentBuilder.DescriptionStrategy {
*/
private static final long serialVersionUID = 1L;
/**
* Original type cache.
* classloader hashcode -> ( typeName -> type cache )
*/
private static final Map<Integer, Map<String, TypeCache>> CLASS_LOADER_TYPE_CACHE = new ConcurrentHashMap<>();
private static final List<String> IGNORED_INTERFACES = Arrays.asList(EnhancedInstance.class.getName());
private static final List<String> IGNORED_FIELDS = Arrays.asList(AbstractClassEnhancePluginDefine.CONTEXT_ATTR_NAME);
private static final List<String> IGNORED_METHODS = Arrays.asList("getSkyWalkingDynamicField", "setSkyWalkingDynamicField");
private MethodList<MethodDescription.InDefinedShape> methods;
private FieldList<FieldDescription.InDefinedShape> fields;
private final String nameTrait;
private ClassLoader classLoader;
private String typeName;
private TypeList.Generic interfaces;
private TypeDescription delegate;
public SWTypeDescriptionWrapper(TypeDescription delegate, String nameTrait) {
public SWTypeDescriptionWrapper(TypeDescription delegate, String nameTrait, ClassLoader classLoader, String typeName) {
this.delegate = delegate;
this.nameTrait = nameTrait;
this.classLoader = classLoader;
this.typeName = typeName;
}
private TypeCache getTypeCache() {
int classLoaderHashCode = classLoader != null ? classLoader.hashCode() : 0;
Map<String, TypeCache> typeCacheMap = CLASS_LOADER_TYPE_CACHE.computeIfAbsent(classLoaderHashCode, k -> new ConcurrentHashMap<>());
TypeCache typeCache = typeCacheMap.computeIfAbsent(typeName, k -> new TypeCache(typeName));
return typeCache;
}
@Override
@ -164,14 +180,16 @@ public class SWDescriptionStrategy implements AgentBuilder.DescriptionStrategy {
public FieldList<FieldDescription.InDefinedShape> getDeclaredFields() {
if (this.fields == null) {
FieldList<FieldDescription.InDefinedShape> declaredFields = delegate.getDeclaredFields();
if (declaredFields.stream()
.anyMatch(f -> f.getName().contains(nameTrait) || IGNORED_FIELDS.contains(f.getName()))) {
// Remove dynamic field tokens generated by SkyWalking
fields = new FieldList.Explicit<>(declaredFields.stream()
.filter(f -> !f.getName().contains(nameTrait) && !IGNORED_FIELDS.contains(f.getName()))
.collect(Collectors.toList()));
} else {
TypeCache typeCache = getTypeCache();
if (typeCache.fieldNames == null) {
// save origin fields
typeCache.fieldNames = declaredFields.stream().map(WithRuntimeName::getName).collect(Collectors.toSet());
fields = declaredFields;
} else {
// return origin fields
fields = new FieldList.Explicit<>(declaredFields.stream()
.filter(f -> typeCache.fieldNames.contains(f.getName()))
.collect(Collectors.toList()));
}
}
return fields;
@ -181,14 +199,17 @@ public class SWDescriptionStrategy implements AgentBuilder.DescriptionStrategy {
public MethodList<MethodDescription.InDefinedShape> getDeclaredMethods() {
if (this.methods == null) {
MethodList<MethodDescription.InDefinedShape> declaredMethods = delegate.getDeclaredMethods();
if (declaredMethods.stream()
.anyMatch(m -> m.getName().contains(nameTrait) || IGNORED_METHODS.contains(m.getName()))) {
// Remove dynamic method tokens generated by SkyWalking
methods = new MethodList.Explicit<>(declaredMethods.stream()
.filter(m -> !m.getName().contains(nameTrait) && !IGNORED_METHODS.contains(m.getName()))
.collect(Collectors.toList()));
} else {
TypeCache typeCache = getTypeCache();
if (typeCache.methodCodes == null) {
// save original methods
typeCache.methodCodes = declaredMethods.stream().map(m -> m.toString().hashCode()).collect(Collectors.toSet());
methods = declaredMethods;
} else {
// return original methods in the same order, remove dynamic method tokens generated by SkyWalking and ByteBuddy
// remove generated methods for delegating superclass methods, such as Jedis.
methods = new MethodList.Explicit<>(declaredMethods.stream()
.filter(m -> typeCache.methodCodes.contains(m.toString().hashCode()))
.collect(Collectors.toList()));
}
}
return methods;
@ -332,4 +353,14 @@ public class SWDescriptionStrategy implements AgentBuilder.DescriptionStrategy {
return delegate.getModifiers();
}
}
static class TypeCache {
private String typeName;
private Set<Integer> methodCodes;
private Set<String> fieldNames;
public TypeCache(String typeName) {
this.typeName = typeName;
}
}
}

View File

@ -0,0 +1,103 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.skywalking.apm.agent.bytebuddy;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.scaffold.MethodGraph;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class SWMethodGraphCompilerDelegate implements MethodGraph.Compiler {
private MethodGraph.Compiler originCompiler;
public SWMethodGraphCompilerDelegate(MethodGraph.Compiler originCompiler) {
this.originCompiler = originCompiler;
}
@Override
public MethodGraph.Linked compile(TypeDefinition typeDefinition) {
MethodGraph.Linked methodGraph = originCompiler.compile(typeDefinition);
return new SWMethodGraph(methodGraph);
}
@Override
@Deprecated
public MethodGraph.Linked compile(TypeDescription typeDescription) {
return originCompiler.compile(typeDescription);
}
@Override
public MethodGraph.Linked compile(TypeDefinition typeDefinition, TypeDescription viewPoint) {
return originCompiler.compile(typeDefinition, viewPoint);
}
@Override
@Deprecated
public MethodGraph.Linked compile(TypeDescription typeDefinition, TypeDescription viewPoint) {
return originCompiler.compile(typeDefinition, viewPoint);
}
static class SWMethodGraph implements MethodGraph.Linked {
private MethodGraph.Linked origin;
public SWMethodGraph(Linked origin) {
this.origin = origin;
}
@Override
public MethodGraph getSuperClassGraph() {
return origin.getSuperClassGraph();
}
@Override
public MethodGraph getInterfaceGraph(TypeDescription typeDescription) {
return origin.getInterfaceGraph(typeDescription);
}
@Override
public Node locate(MethodDescription.SignatureToken token) {
return origin.locate(token);
}
@Override
public NodeList listNodes() {
// sort nodes (methods) to generate same cache field order when re-transform class
NodeList nodeList = origin.listNodes();
List<Node> nodes = nodeList.stream().map(n -> new Pair<Integer, Node>(n.getRepresentative().hashCode(), n))
.sorted(Comparator.comparing(p -> p.first))
.map(p -> p.second)
.collect(Collectors.toList());
return new NodeList(nodes);
}
}
static class Pair<T, V> {
private T first;
private V second;
public Pair(T first, V second) {
this.first = first;
this.second = second;
}
}
}