-
-
Notifications
You must be signed in to change notification settings - Fork 808
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
Customizable advice dispatcher #830
Comments
That's a good idea, but I think I'd take it one step further: I'd insert an
Do you think that covers your use case, too? I have thought about using invokedynamic in this context before and I still think it's a fit. |
That would store the
I don't know tbh. I'm not too familiar with how Do you know how stable the Java 7 support for that is? And I suppose Java 8 < 40 would have problems with that but we don't instrument those Java 8 versions anyways. We've seen some |
Currently, a big downside of non-inlined advices is that the @Advice.OnMethodExit(inline = false)
public static @Advice.Return(readOnly = false) String onMethodExit(@Advice.Argument(0) String arg, @Advice.Return String result) {
return result + arg;
} That would make non-inlined advices much more powerful. |
That second suggestion would be more difficult to accomplish, I'd need to see how to work this out without breaking other assumptions. But I will look into it! As for invokedynamic. Basically, you return a This constant call site is then resolved a single time into the target and never invoked again. The overhead should be zero. And sure, there might be bugs in 7. There probably are since method handles are not used by javac in this version but the entire 7 release is full of bugs that are no longer fixed, it's really long stretch maintenance by now. People should ideally upgrade. But out of curiosity: why does inlining not work for you? It should still be the most reliable form of instrumentation. |
Good question. Let me elaborate a bit on that. It mainly has two reasons. One is visibility issues for helper classes, the other is limitations present in filtering classloaders (OSGi bootdelegation). Visibility issuesWhat's great about inlining advices is that all the inlined code has the same class visibility as the instrumented method. However, it often leads to less than ideal designs where code is copy/pasted or it's quite difficult to have generic abstractions with framework-specific bindings. One example of this would be a header extractor interface that requires a framework-specific implementation for each HTTP framework. Another one is when trying to implement a framework-specific interceptor/callback interface. Most of the workarounds require deep understanding of how classloaders work and which classes are safe to access in which context. That significantly raises the bar for contributions, makes reviews harder and complicates development. Fighting filtering classloadersOne way of solving the OSGi bootdelegation issue is instrumenting OSGi classloaders to always allow bootdelegation for our agent. However, that would be a big maintenance burden and would not work for non-OSGi filtering classloaders. Instead, we only want to inject a single class into the bootstrap classloader - This has the added benefit that the normal classloader hierarchy doesn't see the agent classes. Thus, classpath scanning tools are not slowed down or fail because of agent classes. Our solutionBringing the two points together, the This allows the helpers to have access to both, framework-specific types from the target classloader, and agent classes form the agent classloader. See also this diagram: https://github.com/elastic/apm-agent-java/blob/a9a4611830e8d41e75ddacf247ae9144a0be4669/apm-agent-core/src/main/java/co/elastic/apm/agent/bootstrap/MethodHandleDispatcher.java#L54-L72 By having a dedicated As an added bonus, it allows setting breakpoints in advices and makes exception stack traces are more useful. SummaryIn summary, this makes development much easier, allows for proper abstractions, separates the agent classes from the regular classloader hierarchy, and doesn't require to inject classes into the target classloader.
One workaround could be to use |
I think I'd need another parameter for the classloader of the instrumented class in order to lookup the method handle from the corresponding helper class loader. The |
That should be as easy as: |
Right, thanks! Does the helper class loader architecture sound sane to you? Maybe this is even something that could be included in Byte Buddy itself. I think one missing piece in Byte Buddy is an optional opinionated layer to make the handling of class visibility and bootstrapping an agent, including a proper classloader setup, easier. I assume the |
I just added the possibility to add a bootstrapping class. You can do so by: Advice.withCustomMapping().bootsrap(someMethod).to(...); what causes an invokedynamic call site to be generated for all invocations. I am not yet sure if I want to support reflection-based invocation. It's really messy with the boxing and checked exceptions and the performance is rather poor. Is supporting Java 6 for this form of dispatching crucial? I'd suggest just injecting the whole bunch into the bootstrap loader, probably Java 6 applications are rather static anyways. |
Thanks, I’ll try this out soon. Am I right in assuming that overriding a parameter or return value is not possible using the indy dispatcher? |
No, the indy solution is currently analogous to the regular static dispatcher. I'll look into the possibility to add such return value reuse but I need to check how and if this can be done without bloating the API or implementation. |
The docs about Groovy indy support for Java 7 suggest that it's stable as of Java 7u60. If it turns out it's a similar story for the indy dispatch in Byte Buddy it would be great news as a reflection-based alternative wouldn't be necessary for our use case. https://groovy-lang.org/indy.html
|
The API works great! Just one suggestion: If for whatever reason the target method could not be resolved, it would be nice to be able to return As almost all our advices override parameters or return values, having support for that in non-inlined advices is crucial. |
I'll look into the possibility of adding additional semantics to a method return value. As for returning null: the bootstrap mechanism does not support this. You can however bind a constant call site that drops all arguments and returns null, a primitive 0 or nothing depending on the advice methods return type. Have a look at the lookup API for creating such a constant handle. |
Does the Out of curiosity, what's the use case for the |
Any constructor of an instance of CallSite can be used as a bootstrap method. You could for example subclass constant call site for doing so. And yes, the bootstrap method needs to be visible (and exported) . Otherwise you could breach into protected code using invokedynamic. |
AFAIK, there are currently two ways advices are dispatched.
By default, advice methods are inlined in to the instrumented method.
If the
inline
property is set tofalse
, only a static method call is inserted into the instrumented method that calls the advice method.I'd like to have a way to customize the dispatching.
MethodHandleDispatcher
is a registry where I would make sure to add method handles to all advice methods. Currently, I do that style of dispatching manually by creating an inlined advice that contains theMethodHandle
lookup and invocation. It would be great if I could just write the advice as it was ainline = true
advice and if Byte Buddy could inject the method handle lookup and invocation in a similar way that it currently does for non-inlined advices vianet.bytebuddy.asm.Advice.Dispatcher.Delegating
.This is in the context of this PR: elastic/apm-agent-java#1090
Is that something that would be possible?
The text was updated successfully, but these errors were encountered: