-
Notifications
You must be signed in to change notification settings - Fork 57
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
Clients with a class with postponed initialization in their method signatures cannot be compiled to native #580
Comments
I wonder where that |
Hey @ppalaga Apologies for the late response, just saw this. My suggestion would be to add the following: quarkus.native.additional-build-args = --trace-class-initialization=java.awt.Image,-J-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true That should dump all the Proxy classes under: If you then decompile/view the relevant Proxy source, you should be able to see which interfaces it implements ... that should hopefully lead you to the culprit. |
These are the interfaces I see for the error on my laptop: public final class $Proxy317 extends Proxy implements ImageService, BindingProvider, Closeable, Client { It looks like the ImageService implementation itself is causing your issue. |
Thanks, that helped a lot! I was not aware of Here is the decompiled code:
The static initializer triggers the initialization of |
Hm... To be able to register a given proxy class for runtime initialization, I'd first need to know its name. @zakkak @galderz do you happen to have any idea whether it is possible to instruct GraalVM native-image to postpone the initialization of a generated proxy class for some specific set of interfaces? |
@RequestWrapper
and @ResponseWrapper
cannot be compiled to native
I have updated the title and description with the information we now have about the root cause. |
Hi @ppalaga, not sure how to tackle this. Just wondering if https://www.graalvm.org/22.3/reference-manual/native-image/guides/configure-dynamic-proxies/ (i.e. defining a dynamic proxy manually) could be of any help. Are you defining any |
Could you please explain, how it could help? My understanding is that what Quarkus does via We let GraalVM generate the proxy classes - that works well. We'd just need to be able to postpone the initialization for some of those (such as the ones referencing
Yes, we do produce a |
Correct, I just was not sure whether you were using
+1, unfortunately I am not sure how to achieve this. As you said, we would need to know the generated class name apriori._ |
@ppalaga IIRC it should also be possible to define a package that should be runtime initialized. Would that work in your case? As long as the package only has the generated proxies, you wouldn't (in theory) need to worry about the generated class names. |
Interesting idea. From what I see in the output of |
Looking here (for Java 8, but newer also?), it says:
No idea if this will work, but if you define an empty interface in some package and you make the proxy implement that (even if it's empty), maybe you can end up controlling the package? |
That's a great hint! Thanks a lot @galderz! |
Had a quick go and I think it might do the trick. Say we define a proxy handler like this: static class DynamicInvocationHandler implements InvocationHandler
{
@Override
public Object invoke(Object proxy, Method method, Object[] args)
{
System.out.printf("Invoked method: %s%n", method.getName());
return 42;
}
} And then you invoke: Map proxyInstance = (Map) Proxy.newProxyInstance(
Proxies.class.getClassLoader()
, new Class[] { Map.class }
, new DynamicInvocationHandler()
);
proxyInstance.put("hello", "world");
System.out.println(proxyInstance.getClass()); It'll print:
I defined an empty non-public interface in the package where the proxy handling code lives: package lang.reflect;
interface Marker
{
} I adjusted the proxy code to simply add the marker interface as one of the proxy interfaces: Map proxyInstance = (Map) Proxy.newProxyInstance(
Proxies.class.getClassLoader()
, new Class[] { Map.class, Marker.class }
, new DynamicInvocationHandler()
);
proxyInstance.put("hello", "world");
System.out.println(proxyInstance.getClass()); When I execute that, the proxy package name is adjusted to the package where the non-public interface lives: Invoked method: put
class lang.reflect.$Proxy1 I ran this with Java 17. |
Thanks for verifying that, @galderz! When implementing it, it will be worth looking whether some of those user-provided interfaces happens to be non-public already. I think it is rather unlikely with web services, but we should check anyway. If so, then we cannot add our own marker interface, but we can try to use the user's one (outputting a warning about what we do). Regardless of the above, it would still be handy to have some explicit controls about the init time of the generated proxy classes. Do you think it would be feasible to extend the GraalVM API to pass not only a list of interfaces but also a flag controlling the init time? I mean whether this org.graalvm.nativeimage.hosted.RuntimeProxyCreation.register(Class... interfaces) method could get an overload like |
|
I have a PoC proving that the idea of @galderz with the package private Marker interface is working in CXF. The only gotcha is that the Marker interface needs to be placed in the application code. Otherwise the JVM complains about mismatching class loaders. |
I saw it, but I still wonder what it means? You are looking (a) whether the proposal makes sense or maybe (b) you are looking at others what they come up with or maybe you are looking at something else? |
It just means I'm looking into what you proposed. IOW, need to see if it's feasible, if it makes sense, if there's some other way to achieve the same thing...etc. |
Did you try making |
I finally found some time to try this idea. I first created a simpler test in ppalaga@69ba2bd :
That's enough to reproduce the issue. Then in the subsequent commit I tried to add all/some of the interfaces constituting the dynamic proxy to the postponed init. Those are defined around here, namely:
Requesting runtime init for all of them solved the issue. Which I think would be kind of expected. What I find quite strange is, that requesting runtime init for org.apache.cxf.endpoint.Client only helps too. I have not found any combination without org.apache.cxf.endpoint.Client that would solve it. Any idea @galderz what is so special about org.apache.cxf.endpoint.Client? I even tried to change the ordering in the Proxy def, so that org.apache.cxf.endpoint.Client is not the last one, but it does not make any difference. org.apache.cxf.endpoint.Client is still the only that helps to solve it. |
…ned initialization in their method signatures cannot be compiled to native
…itialization in their method signatures cannot be compiled to native
|
Thinking loud: through org.apache.cxf.endpoint.Client, we can make all the client proxies available in the user app become runtime intialized. We cannot select only those which are known to need it. I wonder whether that can cause other issues. Init time conflicts could happen if some build time initialized class refers to either org.apache.cxf.endpoint.Client or the generated proxy class. The latter would be hard because the name is random and known only to GraalVM. |
…ned initialization in their method signatures cannot be compiled to native
signatures cannot be compiled to native quarkiverse#580
The only issue I can think of here is the potential drop of performance (both peak performance and startup time). You are essentially reducing the number of classes that are being build-time initialized, which can impact the number of optimizations applied and also adds the initialization of those classes to the run-time as well. Are these client proxies on the critical path?
Correct, I don't think this is a big issue and I think it would be easy to fix even if it happened. |
Yes, in SOAP client apps, every request goes through them. |
…ned initialization in their method signatures cannot be compiled to native
signatures cannot be compiled to native quarkiverse#580
signatures cannot be compiled to native quarkiverse#580
In this case it might be worth investigating more. @ppalaga I was checking the details of this issue again and I noticed that the error states that:
Checking Another thing I was thinking about was whether we could get away with a lot of issues if instead of requesting awt to be runtime initialized we requested for it to be runtime **re-**initialized (see oracle/graal#4684 for some more info). |
…ization in their method signatures cannot be compiled to native
signatures cannot be compiled to native #580
@zakkak thanks for looking into this again!
But that would only help with java.awt.Image, woudn't it? But this issue can occur for any runtime initialized class, not only Image and so I'd prefer some general solution. In between I chose the strategy originally proposed by @galderz - i.e. to add a non-public interface to the Proxy definition so that the package of the generated proxy class gets predictable. |
True, it's another tradeoff between fine control of what gets runtime initialized and what not I guess. |
signatures cannot be compiled to native quarkiverse#580
signatures cannot be compiled to native #580
…ned initialization in their method signatures cannot be compiled to native
…ization in their method signatures cannot be compiled to native
Client proxies for the service interfaces (SEI) are generated at runtime by GraalVM native-image. That generated code loads all classes present in all method signatures of the SEI. Hence if we have a SEI like the following
then both
Bar
andBaz
classes get loaded viaClass.forName("acme.Bar")
andClass.forName("acme.Baz")
in the Proxy code generated by GraalVM native-image (see the listing below).In Quarkus all classes are initialized at build time except for those specific classes for which runtime init is requested via
RuntimeInitializedClassBuildItem
. Hence the generated Proxy class is by default also initialized at build time. As a consequence of that all classes mentioned in method signatures of client SEIs are also initialized at build time.If some if those classes needs to be initialized at runtime (such as
java.awt.Image
) and is registered viaRuntimeInitializedClassBuildItem
, then the native compilation fails withError: Classes that should be initialized at run time got initialized during image building
There is a reproducer in #579
The stack trace:
The text was updated successfully, but these errors were encountered: