-
Notifications
You must be signed in to change notification settings - Fork 594
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
Improve bytebuddy class enhance for retransform classes #561
Improve bytebuddy class enhance for retransform classes #561
Conversation
...ache/skywalking/apm/agent/core/plugin/interceptor/enhance/v2/ClassEnhancePluginDefineV2.java
Outdated
Show resolved
Hide resolved
.../org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/DelegateNamingResolver.java
Outdated
Show resolved
Hide resolved
...ebuddy-patch/src/main/java/org/apache/skywalking/apm/agent/bytebuddy/SWClassFileLocator.java
Outdated
Show resolved
Hide resolved
...ebuddy-patch/src/main/java/org/apache/skywalking/apm/agent/bytebuddy/SWClassFileLocator.java
Outdated
Show resolved
Hide resolved
...rc/main/java/org/apache/skywalking/apm/agent/bytebuddy/SWExtractionClassFileTransformer.java
Outdated
Show resolved
Hide resolved
...tch/src/test/java/org/apache/skywalking/apm/agent/bytebuddy/case1/AbstractInterceptTest.java
Outdated
Show resolved
Hide resolved
dependencies: | ||
withPlugins: apm-spring-annotation-plugin-*.jar | ||
withPlugins: apm-*.jar |
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.
With all plugins? Are these necessary? I prefer the explicit names of plugins.
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.
retransform class test scenario should test with more plugins, especially with the jdk plugins, test scenario is not enough currently, may add more cases later.
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.
We should separate them, otherwise, it would be confusing when it fails.
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 think it is ok to load all plugins in this test scenario. In order to ensure the retransform function, plugins that affect retransform need to be identified in time. Retransform class is not a standalone feature and in principle all plugins need to support 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.
The point is not about these two plugin tests, it is about the stability of the testing. The plugins are going to be added/removed from time to time, if you copy all, actually, you don't know which plugins exist, which may cause these tests to fail by other contributions.
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 think we don't cover multiple plugins for one method case, how could that happens?
We now support multiple plugins enhancing the same class, although enhancing different methods, but if the same method/field name is generated, there will be an error.
And even yoi copied all, it doesn't verify this, because many plugins are not activated(not used) in your scenarios.
Yes, and we may need more testcases.
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.
We now support multiple plugins enhancing the same class, although enhancing different methods, but if the same method/field name is generated, there will be an error.
Yes, but that could be error when the plugin tests run too. What your new cases cover is only re-transfer. They are verifying for different cases.
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.
It's not that simple. Different plugin enhancers of the same class may affect each other during re-transforming.
Leave this aside for now and add test cases later if incompatibilities arise.
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 think we don't cover multiple plugins for one method case, how could that happens?
We now support multiple plugins enhancing the same class, although enhancing different methods, but if the same method/field name is generated, there will be an error.
And even yoi copied all, it doesn't verify this, because many plugins are not activated(not used) in your scenarios.
Yes, and we may need more testcases.
@wu-sheng @kylixs Hello,I wonder if multiple plugins enhancing the same class has been supported?
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.
No, it isn't. Also, because we added a new field to the target class, I don't recommend to supporting that.
Could you write pseudocode about the new generated class? Including new field names and types for constructor, instance methods and static methods. |
import sample.mybatis.controller.HotelController$sw$auxiliary$19cja42;
import sample.mybatis.controller.HotelController$sw$auxiliary$p257su0;
import sample.mybatis.domain.Hotel;
import sample.mybatis.service.HotelService;
@RequestMapping(value={"/hotel"})
@RestController
public class HotelController
implements EnhancedInstance {
@Autowired
@Lazy
private HotelService hotelService;
private volatile Object _$EnhancedClassField_ws;
// Interceptor delegate fields
public static volatile /* synthetic */ InstMethodsInter sw$delegate$td03673$ain2do0$8im5jm1;
public static volatile /* synthetic */ InstMethodsInter sw$delegate$td03673$ain2do0$edkmf61;
public static volatile /* synthetic */ ConstructorInter sw$delegate$td03673$ain2do0$qs9unv1;
public static volatile /* synthetic */ InstMethodsInter sw$delegate$td03673$fl4lnk1$m3ia3a2;
public static volatile /* synthetic */ InstMethodsInter sw$delegate$td03673$fl4lnk1$sufrvp1;
public static volatile /* synthetic */ ConstructorInter sw$delegate$td03673$fl4lnk1$cteu7s1;
// Origin method cache value field
private static final /* synthetic */ Method cachedValue$sw$td03673$g5sobj1;
public HotelController() {
this(null);
sw$delegate$td03673$ain2do0$qs9unv1.intercept(this, new Object[0]);
}
private /* synthetic */ HotelController(sw.auxiliary.p257su0 p257su02) {
}
@GetMapping(value={"city/{cityId}"})
public Hotel selectByCityId(@PathVariable(value="cityId") int n) {
// call interceptor with auxiliary type and parameters and origin method object
return (Hotel)sw$delegate$td03673$ain2do0$8im5jm1.intercept(this, new Object[]{n}, new HotelController$sw$auxiliary$19cja42(this, n), cachedValue$sw$td03673$g5sobj1);
}
// Renamed origin method
private /* synthetic */ Hotel sw$origin$selectByCityId$a8458p3(int cityId) {
/*22*/ return this.hotelService.selectByCityId(cityId);
}
// Accessor of renamed origin method, calling from auxiliary type
final /* synthetic */ Hotel sw$origin$selectByCityId$a8458p3$accessor$sw$td03673(int n) {
// Calling renamed origin method
return this.sw$origin$selectByCityId$a8458p3(n);
}
@Override
public Object getSkyWalkingDynamicField() {
return this._$EnhancedClassField_ws;
}
@Override
public void setSkyWalkingDynamicField(Object object) {
this._$EnhancedClassField_ws = object;
}
static {
ClassLoader.getSystemClassLoader().loadClass("org.apache.skywalking.apm.dependencies.net.bytebuddy.dynamic.Nexus").getMethod("initialize", Class.class, Integer.TYPE).invoke(null, HotelController.class, -1072476370);
// Method object
cachedValue$sw$td03673$g5sobj1 = HotelController.class.getMethod("selectByCityId", Integer.TYPE);
}
} Auxiliary type of Constructor : class HotelController$sw$auxiliary$p257su0 {
} Auxiliary type of class HotelController$sw$auxiliary$19cja42
implements Runnable,
Callable {
private HotelController argument0;
private int argument1;
public Object call() throws Exception {
return this.argument0.sw$origin$selectByCityId$a8458p3$accessor$sw$td03673(this.argument1);
}
@Override
public void run() {
this.argument0.sw$origin$selectByCityId$a8458p3$accessor$sw$td03673(this.argument1);
}
HotelController$sw$auxiliary$19cja42(HotelController hotelController, int n) {
this.argument0 = hotelController;
this.argument1 = n;
}
} |
.../org/apache/skywalking/apm/agent/core/plugin/interceptor/enhance/DelegateNamingResolver.java
Outdated
Show resolved
Hide resolved
apm-sniffer/apm-agent/src/main/java/org/apache/skywalking/apm/agent/SkyWalkingAgent.java
Outdated
Show resolved
Hide resolved
apm-sniffer/apm-agent/src/main/java/org/apache/skywalking/apm/agent/SkyWalkingAgent.java
Outdated
Show resolved
Hide resolved
...ebuddy-patch/src/test/java/org/apache/skywalking/apm/agent/bytebuddy/SWClassFileLocator.java
Show resolved
Hide resolved
...y-patch/src/main/java/org/apache/skywalking/apm/agent/bytebuddy/SWMethodNameTransformer.java
Outdated
Show resolved
Hide resolved
...tebuddy-patch/src/test/java/org/apache/skywalking/apm/agent/bytebuddy/ConstructorAdvice.java
Outdated
Show resolved
Hide resolved
I think you misunderstand my point, and did it in the exactly opposite direction of my asking. |
Sometimes an interface is used to identify a certain type of objects, such as |
I don't quite understand this. Can you give me an example? How do you override a method without an interface or a parent class? |
This reverts commit 64b2916.
|
public int getTimeoutSeconds() { | ||
return timeoutSeconds; | ||
} | ||
|
||
public void setTimeoutSeconds(int timeoutSeconds) { | ||
this.timeoutSeconds = timeoutSeconds; | ||
} |
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.
Unused methods.
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.
It's used to config resolve timeout, not every public method need a usage, might need it somewhere later.
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.
This is only a test code, I think usually manual hard code is good enough?
...ebuddy-patch/src/test/java/org/apache/skywalking/apm/agent/bytebuddy/SWClassFileLocator.java
Outdated
Show resolved
Hide resolved
...ebuddy-patch/src/test/java/org/apache/skywalking/apm/agent/bytebuddy/SWClassFileLocator.java
Show resolved
Hide resolved
Generally, this PR should be good enough. Update change logs about the new naming policy of code generation After this PR is merged, I think you should set another PR to update the version to Update main compatibility doc, https://skywalking.apache.org/docs/main/next/en/setup/service-agent/agent-compatibility/ |
To ensure that the hashCode for recreating the XxxInterceptPoint instance is the same as the previous instance, each |
Do you want a consistent and certain hashcode for every method? |
Yes, should return a consistent and certain hashcode from |
Isn't your current algorithm guaranteed? hashcode is not changed once the values of involved fields are not changed. |
The current code is fine, and both ByteBuddy and our custom ElementMatcher implement the toString() method. I worry that in the future, new ElementMatcher subclasses might not implement the toString() method, with no mandatory constraint guarantees. If the hashcode is inconsistent, the retransform will fail. |
That should be able to check through UT. |
Any doc/example about OAP class scan? |
Ref this, this is an annotation scan. You could easily do an inheritance scan(matcher implementations, and/or other class type implementations). |
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.
LGTM. Thanks for making this ready fast. Please update the version in another PR.
@kylixs As you have been involved into core mechanism of the agent, and did significant contributions, I am willing to invite you join the committer team. If you want to, please ping me on the slack |
That's good. I'm interested in that. |
* SWAuxiliaryTypeNamingStrategy Auxiliary type name pattern: <origin_class_name>$<name_trait>$auxiliary$<auxiliary_type_instance_hash> * DelegateNamingResolver Interceptor delegate field name pattern: <name_trait>$delegate$<class_name_hash>$<plugin_define_hash>$<intercept_point_hash> * SWMethodNameTransformer Renamed origin method pattern: <name_trait>$original$<method_name>$<method_description_hash> * SWImplementationContextFactory Method cache value field pattern: cachedValue$<name_trait>$<origin_class_name_hash>$<field_value_hash> Accessor method name pattern: <renamed_origin_method>$accessor$<name_trait>$<origin_class_name_hash> Here is an example of manipulated enhanced class with new naming policies of auxiliary classes, fields, and methods ```java import sample.mybatis.controller.HotelController$sw$auxiliary$19cja42; import sample.mybatis.controller.HotelController$sw$auxiliary$p257su0; import sample.mybatis.domain.Hotel; import sample.mybatis.service.HotelService; @RequestMapping(value={"/hotel"}) @RestController public class HotelController implements EnhancedInstance { @Autowired @lazy private HotelService hotelService; private volatile Object _$EnhancedClassField_ws; // Interceptor delegate fields public static volatile /* synthetic */ InstMethodsInter sw$delegate$td03673$ain2do0$8im5jm1; public static volatile /* synthetic */ InstMethodsInter sw$delegate$td03673$ain2do0$edkmf61; public static volatile /* synthetic */ ConstructorInter sw$delegate$td03673$ain2do0$qs9unv1; public static volatile /* synthetic */ InstMethodsInter sw$delegate$td03673$fl4lnk1$m3ia3a2; public static volatile /* synthetic */ InstMethodsInter sw$delegate$td03673$fl4lnk1$sufrvp1; public static volatile /* synthetic */ ConstructorInter sw$delegate$td03673$fl4lnk1$cteu7s1; // Origin method cache value field private static final /* synthetic */ Method cachedValue$sw$td03673$g5sobj1; public HotelController() { this(null); sw$delegate$td03673$ain2do0$qs9unv1.intercept(this, new Object[0]); } private /* synthetic */ HotelController(sw.auxiliary.p257su0 p257su02) { } @GetMapping(value={"city/{cityId}"}) public Hotel selectByCityId(@PathVariable(value="cityId") int n) { // call interceptor with auxiliary type and parameters and origin method object return (Hotel)sw$delegate$td03673$ain2do0$8im5jm1.intercept(this, new Object[]{n}, new HotelController$sw$auxiliary$19cja42(this, n), cachedValue$sw$td03673$g5sobj1); } // Renamed origin method private /* synthetic */ Hotel sw$origin$selectByCityId$a8458p3(int cityId) { /*22*/ return this.hotelService.selectByCityId(cityId); } // Accessor of renamed origin method, calling from auxiliary type final /* synthetic */ Hotel sw$origin$selectByCityId$a8458p3$accessor$sw$td03673(int n) { // Calling renamed origin method return this.sw$origin$selectByCityId$a8458p3(n); } @OverRide public Object getSkyWalkingDynamicField() { return this._$EnhancedClassField_ws; } @OverRide public void setSkyWalkingDynamicField(Object object) { this._$EnhancedClassField_ws = object; } static { ClassLoader.getSystemClassLoader().loadClass("org.apache.skywalking.apm.dependencies.net.bytebuddy.dynamic.Nexus").getMethod("initialize", Class.class, Integer.TYPE).invoke(null, HotelController.class, -1072476370); // Method object cachedValue$sw$td03673$g5sobj1 = HotelController.class.getMethod("selectByCityId", Integer.TYPE); } } ``` Auxiliary type of Constructor : ```java class HotelController$sw$auxiliary$p257su0 { } ``` Auxiliary type of `selectByCityId` method: ```java class HotelController$sw$auxiliary$19cja42 implements Runnable, Callable { private HotelController argument0; private int argument1; public Object call() throws Exception { return this.argument0.sw$origin$selectByCityId$a8458p3$accessor$sw$td03673(this.argument1); } @OverRide public void run() { this.argument0.sw$origin$selectByCityId$a8458p3$accessor$sw$td03673(this.argument1); } HotelController$sw$auxiliary$19cja42(HotelController hotelController, int n) { this.argument0 = hotelController; this.argument1 = n; } } ```
Improve bytebuddy class enhance for retransform classes
CHANGES
log.