Skip to content

Commit 4f2f81e

Browse files
authored
Fix re-transform bug when enhanced class proxy parent method (#659)
1 parent 7aec682 commit 4f2f81e

File tree

6 files changed

+266
-34
lines changed

6 files changed

+266
-34
lines changed

CHANGES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Release Notes.
88
* Fix NoSuchMethodError in mvc-annotation-commons and change deprecated method.
99
* fix forkjoinpool plugin in JDK11。
1010
* Support for tracing spring-cloud-gateway 4.x in gateway-4.x-plugin.
11-
11+
* Fix re-transform bug when plugin enhanced class proxy parent method.
1212

1313
#### Documentation
1414

apm-sniffer/apm-agent/src/main/java/org/apache/skywalking/apm/agent/SkyWalkingAgent.java

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@
3232
import net.bytebuddy.description.NamedElement;
3333
import net.bytebuddy.description.type.TypeDescription;
3434
import net.bytebuddy.dynamic.DynamicType;
35+
import net.bytebuddy.dynamic.scaffold.MethodGraph;
3536
import net.bytebuddy.dynamic.scaffold.TypeValidation;
3637
import org.apache.skywalking.apm.agent.bytebuddy.SWAuxiliaryTypeNamingStrategy;
3738
import net.bytebuddy.implementation.SWImplementationContextFactory;
3839
import net.bytebuddy.matcher.ElementMatcher;
3940
import net.bytebuddy.matcher.ElementMatchers;
4041
import net.bytebuddy.utility.JavaModule;
42+
import org.apache.skywalking.apm.agent.bytebuddy.SWMethodGraphCompilerDelegate;
4143
import org.apache.skywalking.apm.agent.bytebuddy.SWMethodNameTransformer;
4244
import org.apache.skywalking.apm.agent.core.boot.AgentPackageNotFoundException;
4345
import org.apache.skywalking.apm.agent.core.boot.ServiceManager;
@@ -99,6 +101,23 @@ public static void premain(String agentArgs, Instrumentation instrumentation) th
99101
return;
100102
}
101103

104+
try {
105+
installClassTransformer(instrumentation, pluginFinder);
106+
} catch (Exception e) {
107+
LOGGER.error(e, "Skywalking agent installed class transformer failure.");
108+
}
109+
110+
try {
111+
ServiceManager.INSTANCE.boot();
112+
} catch (Exception e) {
113+
LOGGER.error(e, "Skywalking agent boot failure.");
114+
}
115+
116+
Runtime.getRuntime()
117+
.addShutdownHook(new Thread(ServiceManager.INSTANCE::shutdown, "skywalking service shutdown thread"));
118+
}
119+
120+
static void installClassTransformer(Instrumentation instrumentation, PluginFinder pluginFinder) throws Exception {
102121
LOGGER.info("Skywalking agent begin to install transformer ...");
103122

104123
AgentBuilder agentBuilder = newAgentBuilder().ignore(
@@ -116,15 +135,13 @@ public static void premain(String agentArgs, Instrumentation instrumentation) th
116135
try {
117136
agentBuilder = BootstrapInstrumentBoost.inject(pluginFinder, instrumentation, agentBuilder, edgeClasses);
118137
} catch (Exception e) {
119-
LOGGER.error(e, "SkyWalking agent inject bootstrap instrumentation failure. Shutting down.");
120-
return;
138+
throw new Exception("SkyWalking agent inject bootstrap instrumentation failure. Shutting down.", e);
121139
}
122140

123141
try {
124142
agentBuilder = JDK9ModuleExporter.openReadEdge(instrumentation, agentBuilder, edgeClasses);
125143
} catch (Exception e) {
126-
LOGGER.error(e, "SkyWalking agent open read edge in JDK 9+ failure. Shutting down.");
127-
return;
144+
throw new Exception("SkyWalking agent open read edge in JDK 9+ failure. Shutting down.", e);
128145
}
129146

130147
agentBuilder.type(pluginFinder.buildMatch())
@@ -137,15 +154,6 @@ public static void premain(String agentArgs, Instrumentation instrumentation) th
137154
PluginFinder.pluginInitCompleted();
138155

139156
LOGGER.info("Skywalking agent transformer has installed.");
140-
141-
try {
142-
ServiceManager.INSTANCE.boot();
143-
} catch (Exception e) {
144-
LOGGER.error(e, "Skywalking agent boot failure.");
145-
}
146-
147-
Runtime.getRuntime()
148-
.addShutdownHook(new Thread(ServiceManager.INSTANCE::shutdown, "skywalking service shutdown thread"));
149157
}
150158

151159
/**
@@ -156,7 +164,8 @@ private static AgentBuilder newAgentBuilder() {
156164
final ByteBuddy byteBuddy = new ByteBuddy()
157165
.with(TypeValidation.of(Config.Agent.IS_OPEN_DEBUGGING_CLASS))
158166
.with(new SWAuxiliaryTypeNamingStrategy(NAME_TRAIT))
159-
.with(new SWImplementationContextFactory(NAME_TRAIT));
167+
.with(new SWImplementationContextFactory(NAME_TRAIT))
168+
.with(new SWMethodGraphCompilerDelegate(MethodGraph.Compiler.DEFAULT));
160169

161170
return new SWAgentBuilderDefault(byteBuddy, new SWNativeMethodStrategy(NAME_TRAIT))
162171
.with(new SWDescriptionStrategy(NAME_TRAIT));

apm-sniffer/apm-sdk-plugin/jedis-plugins/jedis-2.x-3.x-plugin/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,11 @@
4242
<version>${jedis.version}</version>
4343
<scope>provided</scope>
4444
</dependency>
45+
<dependency>
46+
<groupId>org.apache.skywalking</groupId>
47+
<artifactId>apm-agent</artifactId>
48+
<version>${project.version}</version>
49+
<scope>test</scope>
50+
</dependency>
4551
</dependencies>
4652
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.skywalking.apm.agent;
19+
20+
import com.google.common.base.Stopwatch;
21+
import net.bytebuddy.agent.ByteBuddyAgent;
22+
import org.apache.skywalking.apm.agent.core.logging.core.SystemOutWriter;
23+
import org.apache.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine;
24+
import org.apache.skywalking.apm.agent.core.plugin.ByteBuddyCoreClasses;
25+
import org.apache.skywalking.apm.agent.core.plugin.PluginFinder;
26+
import org.apache.skywalking.apm.plugin.jedis.v3.define.JedisInstrumentation;
27+
import org.junit.Assert;
28+
import org.junit.Test;
29+
import redis.clients.jedis.Jedis;
30+
31+
import java.lang.instrument.Instrumentation;
32+
import java.util.Arrays;
33+
import java.util.List;
34+
import java.util.concurrent.TimeUnit;
35+
36+
public class JedisInstrumentationTest {
37+
38+
@Test
39+
public void test() throws Exception {
40+
// tested plugins
41+
List<AbstractClassEnhancePluginDefine> plugins = Arrays.asList(new JedisInstrumentation());
42+
43+
// remove shade prefix
44+
String[] classes = ByteBuddyCoreClasses.CLASSES;
45+
for (int i = 0; i < classes.length; i++) {
46+
classes[i] = classes[i].replaceFirst("org.apache.skywalking.apm.dependencies.", "");
47+
}
48+
49+
Instrumentation instrumentation = ByteBuddyAgent.install();
50+
SkyWalkingAgent.installClassTransformer(instrumentation, new PluginFinder(plugins));
51+
52+
// first load
53+
Jedis jedis = new Jedis();
54+
try {
55+
jedis.get("mykey");
56+
} catch (Exception e) {
57+
Assert.assertTrue(e.toString(), e.toString().contains("JedisConnectionException"));
58+
}
59+
60+
log("Do re-transform class : redis.clients.jedis.Jedis ..");
61+
Stopwatch stopwatch = Stopwatch.createStarted();
62+
63+
// re-transform class
64+
for (int i = 0; i < 4; i++) {
65+
stopwatch.reset();
66+
stopwatch.start();
67+
instrumentation.retransformClasses(Jedis.class);
68+
long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
69+
log("Re-transform class cost: " + elapsed);
70+
}
71+
72+
// test after re-transform class
73+
try {
74+
jedis.get("mykey");
75+
} catch (Exception e) {
76+
Assert.assertTrue(e.toString(), e.toString().contains("JedisConnectionException"));
77+
}
78+
}
79+
80+
private void log(String message) {
81+
SystemOutWriter.INSTANCE.write(message);
82+
}
83+
}

apm-sniffer/bytebuddy-patch/src/main/java/net/bytebuddy/agent/builder/SWDescriptionStrategy.java

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import net.bytebuddy.pool.TypePool;
3030
import net.bytebuddy.utility.JavaModule;
3131
import net.bytebuddy.utility.nullability.MaybeNull;
32-
import org.apache.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine;
3332
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
3433

3534
import java.io.Serializable;
@@ -38,6 +37,8 @@
3837
import java.util.HashMap;
3938
import java.util.List;
4039
import java.util.Map;
40+
import java.util.Set;
41+
import java.util.concurrent.ConcurrentHashMap;
4142
import java.util.stream.Collectors;
4243

4344
/**
@@ -109,7 +110,7 @@ public TypeDescription apply(String name,
109110
}
110111
}
111112
// wrap result
112-
return new SWTypeDescriptionWrapper(delegate.apply(name, type, typePool, circularityLock, classLoader, module), nameTrait);
113+
return new SWTypeDescriptionWrapper(delegate.apply(name, type, typePool, circularityLock, classLoader, module), nameTrait, classLoader, name);
113114
}
114115

115116
/**
@@ -122,25 +123,40 @@ static class SWTypeDescriptionWrapper extends TypeDescription.AbstractBase imple
122123
*/
123124
private static final long serialVersionUID = 1L;
124125

125-
private static final List<String> IGNORED_INTERFACES = Arrays.asList(EnhancedInstance.class.getName());
126-
127-
private static final List<String> IGNORED_FIELDS = Arrays.asList(AbstractClassEnhancePluginDefine.CONTEXT_ATTR_NAME);
126+
/**
127+
* Original type cache.
128+
* classloader hashcode -> ( typeName -> type cache )
129+
*/
130+
private static final Map<Integer, Map<String, TypeCache>> CLASS_LOADER_TYPE_CACHE = new ConcurrentHashMap<>();
128131

129-
private static final List<String> IGNORED_METHODS = Arrays.asList("getSkyWalkingDynamicField", "setSkyWalkingDynamicField");
132+
private static final List<String> IGNORED_INTERFACES = Arrays.asList(EnhancedInstance.class.getName());
130133

131134
private MethodList<MethodDescription.InDefinedShape> methods;
132135

133136
private FieldList<FieldDescription.InDefinedShape> fields;
134137

135138
private final String nameTrait;
136139

140+
private ClassLoader classLoader;
141+
142+
private String typeName;
143+
137144
private TypeList.Generic interfaces;
138145

139146
private TypeDescription delegate;
140147

141-
public SWTypeDescriptionWrapper(TypeDescription delegate, String nameTrait) {
148+
public SWTypeDescriptionWrapper(TypeDescription delegate, String nameTrait, ClassLoader classLoader, String typeName) {
142149
this.delegate = delegate;
143150
this.nameTrait = nameTrait;
151+
this.classLoader = classLoader;
152+
this.typeName = typeName;
153+
}
154+
155+
private TypeCache getTypeCache() {
156+
int classLoaderHashCode = classLoader != null ? classLoader.hashCode() : 0;
157+
Map<String, TypeCache> typeCacheMap = CLASS_LOADER_TYPE_CACHE.computeIfAbsent(classLoaderHashCode, k -> new ConcurrentHashMap<>());
158+
TypeCache typeCache = typeCacheMap.computeIfAbsent(typeName, k -> new TypeCache(typeName));
159+
return typeCache;
144160
}
145161

146162
@Override
@@ -164,14 +180,16 @@ public TypeList.Generic getInterfaces() {
164180
public FieldList<FieldDescription.InDefinedShape> getDeclaredFields() {
165181
if (this.fields == null) {
166182
FieldList<FieldDescription.InDefinedShape> declaredFields = delegate.getDeclaredFields();
167-
if (declaredFields.stream()
168-
.anyMatch(f -> f.getName().contains(nameTrait) || IGNORED_FIELDS.contains(f.getName()))) {
169-
// Remove dynamic field tokens generated by SkyWalking
183+
TypeCache typeCache = getTypeCache();
184+
if (typeCache.fieldNames == null) {
185+
// save origin fields
186+
typeCache.fieldNames = declaredFields.stream().map(WithRuntimeName::getName).collect(Collectors.toSet());
187+
fields = declaredFields;
188+
} else {
189+
// return origin fields
170190
fields = new FieldList.Explicit<>(declaredFields.stream()
171-
.filter(f -> !f.getName().contains(nameTrait) && !IGNORED_FIELDS.contains(f.getName()))
191+
.filter(f -> typeCache.fieldNames.contains(f.getName()))
172192
.collect(Collectors.toList()));
173-
} else {
174-
fields = declaredFields;
175193
}
176194
}
177195
return fields;
@@ -181,14 +199,17 @@ public FieldList<FieldDescription.InDefinedShape> getDeclaredFields() {
181199
public MethodList<MethodDescription.InDefinedShape> getDeclaredMethods() {
182200
if (this.methods == null) {
183201
MethodList<MethodDescription.InDefinedShape> declaredMethods = delegate.getDeclaredMethods();
184-
if (declaredMethods.stream()
185-
.anyMatch(m -> m.getName().contains(nameTrait) || IGNORED_METHODS.contains(m.getName()))) {
186-
// Remove dynamic method tokens generated by SkyWalking
202+
TypeCache typeCache = getTypeCache();
203+
if (typeCache.methodCodes == null) {
204+
// save original methods
205+
typeCache.methodCodes = declaredMethods.stream().map(m -> m.toString().hashCode()).collect(Collectors.toSet());
206+
methods = declaredMethods;
207+
} else {
208+
// return original methods in the same order, remove dynamic method tokens generated by SkyWalking and ByteBuddy
209+
// remove generated methods for delegating superclass methods, such as Jedis.
187210
methods = new MethodList.Explicit<>(declaredMethods.stream()
188-
.filter(m -> !m.getName().contains(nameTrait) && !IGNORED_METHODS.contains(m.getName()))
211+
.filter(m -> typeCache.methodCodes.contains(m.toString().hashCode()))
189212
.collect(Collectors.toList()));
190-
} else {
191-
methods = declaredMethods;
192213
}
193214
}
194215
return methods;
@@ -332,4 +353,14 @@ public int getModifiers() {
332353
return delegate.getModifiers();
333354
}
334355
}
356+
357+
static class TypeCache {
358+
private String typeName;
359+
private Set<Integer> methodCodes;
360+
private Set<String> fieldNames;
361+
362+
public TypeCache(String typeName) {
363+
this.typeName = typeName;
364+
}
365+
}
335366
}

0 commit comments

Comments
 (0)