Skip to content
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

Closed
ppalaga opened this issue Oct 28, 2022 · 30 comments · Fixed by #769 or #771
Milestone

Comments

@ppalaga
Copy link
Contributor

ppalaga commented Oct 28, 2022

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

@WebService
interface Foo {
  Bar doSomething(Baz baz);
}

then both Bar and Baz classes get loaded via Class.forName("acme.Bar") and Class.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 via RuntimeInitializedClassBuildItem, then the native compilation fails with Error: Classes that should be initialized at run time got initialized during image building

There is a reproducer in #579

The stack trace:

[INFO] [stdout] ========================================================================================================================
[INFO] [stdout] GraalVM Native Image: Generating 'quarkus-cxf-integration-test-mtom-awt-1.6.0-SNAPSHOT-runner' (executable)...
[INFO] [stdout] ========================================================================================================================
[INFO] [stdout] [1/7] Initializing...                                                                                    (7.8s @ 0.17GB)
[INFO] [stdout]  Version info: 'GraalVM 22.2.0 Java 17 CE'
[INFO] [stdout]  Java version info: '17.0.4+8-jvmci-22.2-b06'
[INFO] [stdout]  C compiler: gcc (redhat, x86_64, 8.5.0)
[INFO] [stdout]  Garbage collector: Serial GC
[INFO] [stdout]  6 user-specific feature(s)
[INFO] [stdout]  - io.quarkus.awt.runtime.graal.AwtFeature
[INFO] [stdout]  - io.quarkus.awt.runtime.graal.DarwinAwtFeature
[INFO] [stdout]  - io.quarkus.runner.Feature: Auto-generated class by Quarkus from the existing extensions
[INFO] [stdout]  - io.quarkus.runtime.graal.DisableLoggingFeature: Disables INFO logging during the analysis phase for the [org.jboss.threads] categories
[INFO] [stdout]  - io.quarkus.runtime.graal.ResourcesFeature: Register each line in META-INF/quarkus-native-resources.txt as a resource on Substrate VM
[INFO] [stdout]  - org.graalvm.home.HomeFinderFeature: Finds GraalVM paths and its version number
[INFO] [stdout] [2/7] Performing analysis...  []                                                                         (5.7s @ 0.83GB)
[INFO] [stdout]    5,601 (89.44%) of  6,262 classes reachable
[INFO] [stdout]    7,089 (56.75%) of 12,491 fields reachable
[INFO] [stdout]   22,941 (77.54%) of 29,585 methods reachable
[INFO] [stdout]      873 classes,     0 fields, and     0 methods registered for reflection
[INFO] [stdout]        2 native libraries: m, stdc++
[INFO] [stdout] 
[WARN] [stderr] Error: Classes that should be initialized at run time got initialized during image building:
[WARN] [stderr]  java.awt.Image the class was requested to be initialized at run time (Required for sun.text.bidi.BidiBase.NumericShapings). jdk.proxy4.$Proxy131 caused initialization of this class with the following trace: 
[WARN] [stderr]         at java.awt.Image.<clinit>(Image.java:58)
[WARN] [stderr]         at org.graalvm.nativeimage.builder/com.oracle.svm.core.util.UserError.abort(UserError.java:72)
[WARN] [stderr]         at java.lang.Class.forName0(Unknown Source)
[WARN] [stderr]         at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.classinitialization.ConfigurableClassInitialization.checkDelayedInitialization(ConfigurableClassInitialization.java:554)
[WARN] [stderr]         at java.lang.Class.forName(Class.java:375)
[WARN] [stderr]         at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.classinitialization.ClassInitializationFeature.duringAnalysis(ClassInitializationFeature.java:172)
[WARN] [stderr]         at jdk.proxy4.$Proxy131.<clinit>(Unknown Source)
[WARN] [stderr]         at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGenerator.lambda$runPointsToAnalysis$10(NativeImageGenerator.java:734)
[WARN] [stderr] 
[WARN] [stderr]         at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.FeatureHandler.forEachFeature(FeatureHandler.java:78)
[WARN] [stderr] 
[WARN] [stderr]         at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGenerator.lambda$runPointsToAnalysis$11(NativeImageGenerator.java:734)
[WARN] [stderr] com.oracle.svm.core.util.UserError$UserException: Classes that should be initialized at run time got initialized during image building:
[WARN] [stderr]         at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.PointsToAnalysis.runAnalysis(PointsToAnalysis.java:755)
[WARN] [stderr]  java.awt.Image the class was requested to be initialized at run time (Required for sun.text.bidi.BidiBase.NumericShapings). jdk.proxy4.$Proxy131 caused initialization of this class with the following trace: 
[WARN] [stderr]         at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGenerator.runPointsToAnalysis(NativeImageGenerator.java:731)
[WARN] [stderr]         at java.awt.Image.<clinit>(Image.java:58)
[WARN] [stderr]         at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGenerator.doRun(NativeImageGenerator.java:564)
[WARN] [stderr]         at java.lang.Class.forName0(Unknown Source)
[WARN] [stderr]         at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGenerator.run(NativeImageGenerator.java:521)
[WARN] [stderr]         at java.lang.Class.forName(Class.java:375)
[WARN] [stderr]         at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGeneratorRunner.buildImage(NativeImageGeneratorRunner.java:407)
[WARN] [stderr]         at jdk.proxy4.$Proxy131.<clinit>(Unknown Source)
[WARN] [stderr]         at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGeneratorRunner.build(NativeImageGeneratorRunner.java:585)
[WARN] [stderr] 
[WARN] [stderr]         at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGeneratorRunner.main(NativeImageGeneratorRunner.java:128)
[INFO] [stdout] ------------------------------------------------------------------------------------------------------------------------
[WARN] [stderr] 
[INFO] [stdout]                         0.7s (4.5% of total time) in 19 GCs | Peak RSS: 2.29GB | CPU load: 9.89
[INFO] [stdout] ========================================================================================================================
[INFO] [stdout] Failed generating 'quarkus-cxf-integration-test-mtom-awt-1.6.0-SNAPSHOT-runner' after 14.0s.
[WARN] [stderr] Error: Image build request failed with exit status 1
@ppalaga
Copy link
Contributor Author

ppalaga commented Oct 28, 2022

I wonder where that jdk.proxy4.$Proxy131 class that requests the initialization of java.awt.Image comes from? Any idea @shumonsharif ?

@shumonsharif
Copy link
Contributor

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:
target/quarkus-cxf-integration-test-mtom-awt-1.6.0-SNAPSHOT-native-image-source-jar/com/sun/proxy

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.

@shumonsharif
Copy link
Contributor

shumonsharif commented Oct 29, 2022

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.

@ppalaga
Copy link
Contributor Author

ppalaga commented Oct 30, 2022

Thanks, that helped a lot! I was not aware of -J-Djdk.proxy.ProxyGenerator.saveGeneratedFiles

Here is the decompiled code:

package jdk.proxy4;

...

public final class $Proxy133 extends Proxy implements ImageService, BindingProvider, Closeable, Client {
   private static final Method m0;
   private static final Method m1;
   private static final Method m2;
   private static final Method m3;
   private static final Method m4;
   private static final Method m5;
   private static final Method m6;
   private static final Method m7;
   private static final Method m8;
   private static final Method m9;
   private static final Method m10;
   private static final Method m11;
   private static final Method m12;
   private static final Method m13;
   private static final Method m14;
   private static final Method m15;
   private static final Method m16;
   private static final Method m17;
   private static final Method m18;
   private static final Method m19;
   private static final Method m20;
   private static final Method m21;
   private static final Method m22;
   private static final Method m23;
   private static final Method m24;
   private static final Method m25;
   private static final Method m26;
   private static final Method m27;
   private static final Method m28;
   private static final Method m29;
   private static final Method m30;
   private static final Method m31;
   private static final Method m32;
   private static final Method m33;
   private static final Method m34;
   private static final Method m35;
   private static final Method m36;
   private static final Method m37;
   private static final Method m38;
   private static final Method m39;
   private static final Method m40;

...

   static {
      try {
         m0 = Class.forName("java.lang.Object").getMethod("hashCode");
         m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
         m2 = Class.forName("java.lang.Object").getMethod("toString");
         m3 = Class.forName("io.quarkiverse.cxf.it.ws.mtom.awt.server.ImageService").getMethod("downloadImage", Class.forName("java.lang.String"));
         m4 = Class.forName("io.quarkiverse.cxf.it.ws.mtom.awt.server.ImageService").getMethod("uploadImage", Class.forName("java.awt.Image"), Class.forName("java.lang.String"));
         m5 = Class.forName("javax.xml.ws.BindingProvider").getMethod("getEndpointReference", Class.forName("java.lang.Class"));
         m6 = Class.forName("javax.xml.ws.BindingProvider").getMethod("getEndpointReference");
         m7 = Class.forName("javax.xml.ws.BindingProvider").getMethod("getRequestContext");
         m8 = Class.forName("javax.xml.ws.BindingProvider").getMethod("getResponseContext");
         m9 = Class.forName("javax.xml.ws.BindingProvider").getMethod("getBinding");
         m10 = Class.forName("java.io.Closeable").getMethod("close");
         m11 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("getConduit");
         m12 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("setExecutor", Class.forName("java.util.concurrent.Executor"));
         m13 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("getContexts");
         m14 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("invokeWrapped", Class.forName("org.apache.cxf.endpoint.ClientCallback"), Class.forName("java.lang.String"), Class.forName("[Ljava.lang.Object;"));
         m15 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("invokeWrapped", Class.forName("org.apache.cxf.endpoint.ClientCallback"), Class.forName("javax.xml.namespace.QName"), Class.forName("[Ljava.lang.Object;"));
         m16 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("invokeWrapped", Class.forName("javax.xml.namespace.QName"), Class.forName("[Ljava.lang.Object;"));
         m17 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("invokeWrapped", Class.forName("java.lang.String"), Class.forName("[Ljava.lang.Object;"));
         m18 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("setThreadLocalRequestContext", Boolean.TYPE);
         m19 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("isThreadLocalRequestContext");
         m20 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("getConduitSelector");
         m21 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("setConduitSelector", Class.forName("org.apache.cxf.endpoint.ConduitSelector"));
         m22 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("getBus");
         m23 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("getEndpoint");
         m24 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("invoke", Class.forName("java.lang.String"), Class.forName("[Ljava.lang.Object;"));
         m25 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("invoke", Class.forName("org.apache.cxf.endpoint.ClientCallback"), Class.forName("java.lang.String"), Class.forName("[Ljava.lang.Object;"));
         m26 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("invoke", Class.forName("org.apache.cxf.service.model.BindingOperationInfo"), Class.forName("[Ljava.lang.Object;"), Class.forName("java.util.Map"), Class.forName("org.apache.cxf.message.Exchange"));
         m27 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("invoke", Class.forName("org.apache.cxf.service.model.BindingOperationInfo"), Class.forName("[Ljava.lang.Object;"), Class.forName("java.util.Map"));
         m28 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("invoke", Class.forName("org.apache.cxf.service.model.BindingOperationInfo"), Class.forName("[Ljava.lang.Object;"));
         m29 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("invoke", Class.forName("javax.xml.namespace.QName"), Class.forName("[Ljava.lang.Object;"));
         m30 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("invoke", Class.forName("org.apache.cxf.endpoint.ClientCallback"), Class.forName("org.apache.cxf.service.model.BindingOperationInfo"), Class.forName("[Ljava.lang.Object;"), Class.forName("org.apache.cxf.message.Exchange"));
         m31 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("invoke", Class.forName("org.apache.cxf.endpoint.ClientCallback"), Class.forName("org.apache.cxf.service.model.BindingOperationInfo"), Class.forName("[Ljava.lang.Object;"), Class.forName("java.util.Map"), Class.forName("org.apache.cxf.message.Exchange"));
         m32 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("invoke", Class.forName("org.apache.cxf.endpoint.ClientCallback"), Class.forName("org.apache.cxf.service.model.BindingOperationInfo"), Class.forName("[Ljava.lang.Object;"), Class.forName("java.util.Map"));
         m33 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("invoke", Class.forName("org.apache.cxf.endpoint.ClientCallback"), Class.forName("org.apache.cxf.service.model.BindingOperationInfo"), Class.forName("[Ljava.lang.Object;"));
         m34 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("invoke", Class.forName("org.apache.cxf.endpoint.ClientCallback"), Class.forName("javax.xml.namespace.QName"), Class.forName("[Ljava.lang.Object;"));
         m35 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("destroy");
         m36 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("getOutFaultInterceptors");
         m37 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("getInInterceptors");
         m38 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("getInFaultInterceptors");
         m39 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("getOutInterceptors");
         m40 = Class.forName("org.apache.cxf.endpoint.Client").getMethod("onMessage", Class.forName("org.apache.cxf.message.Message"));
      } catch (NoSuchMethodException var2) {
         throw new NoSuchMethodError(var2.getMessage());
      } catch (ClassNotFoundException var3) {
         throw new NoClassDefFoundError(var3.getMessage());
      }
   }
...
}

The static initializer triggers the initialization of java.awt.Image by calling Class.forName("java.awt.Image").
Let me ponder how can we fix this.

@ppalaga
Copy link
Contributor Author

ppalaga commented Oct 30, 2022

Hm... To be able to register a given proxy class for runtime initialization, I'd first need to know its name.
But I am not finding any way how to get the proxy class name of the given set of interfaces.

@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?

@ppalaga ppalaga changed the title Service with @RequestWrapper and @ResponseWrapper cannot be compiled to native Clients with a class with postponed initialization in their method signatures cannot be compiled to native Oct 31, 2022
@ppalaga
Copy link
Contributor Author

ppalaga commented Oct 31, 2022

I have updated the title and description with the information we now have about the root cause.

@zakkak
Copy link

zakkak commented Oct 31, 2022

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 io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem for the said proxy?

@ppalaga
Copy link
Contributor Author

ppalaga commented Oct 31, 2022

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.

Could you please explain, how it could help? My understanding is that what Quarkus does via NativeImageProxyDefinitionBuildItem has the very same effect as the JSON file mentioned in https://www.graalvm.org/22.3/reference-manual/native-image/guides/configure-dynamic-proxies/

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 java.awt.Image).

Are you defining any io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem for the said proxy?

Yes, we do produce a NativeImageProxyDefinitionBuildItem for every client SEI around here: https://github.com/quarkiverse/quarkus-cxf/blob/main/extensions/core/deployment/src/main/java/io/quarkiverse/cxf/deployment/CxfClientProcessor.java#L101-L102

@zakkak
Copy link

zakkak commented Oct 31, 2022

Could you please explain, how it could help? My understanding is that what Quarkus does via NativeImageProxyDefinitionBuildItem has the very same effect as the JSON file mentioned in https://www.graalvm.org/22.3/reference-manual/native-image/guides/configure-dynamic-proxies/

Correct, I just was not sure whether you were using NativeImageProxyDefinitionBuildItem or not.

We'd just need to be able to postpone the initialization for some of those (such as the ones referencing java.awt.Image).

+1, unfortunately I am not sure how to achieve this. As you said, we would need to know the generated class name apriori._

@galderz
Copy link

galderz commented Nov 1, 2022

@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.

@ppalaga
Copy link
Contributor Author

ppalaga commented Nov 1, 2022

Interesting idea. From what I see in the output of -J-Djdk.proxy.ProxyGenerator.saveGeneratedFiles, it looks like also the package names are not 100% deterministic. Ours is jdk.proxy4 but I guess it could be some other number. Postponing the initialization of all generared proxy classes could cause some new conflicts.

@galderz
Copy link

galderz commented Nov 1, 2022

Looking here (for Java 8, but newer also?), it says:

If a proxy class implements a non-public interface, then it will be defined in the same package as that interface. Otherwise, the package of a proxy class is also unspecified. Note that package sealing will not prevent a proxy class from being successfully defined in a particular package at runtime, and neither will classes already defined in the same class loader and the same package with particular signers.

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?

@ppalaga
Copy link
Contributor Author

ppalaga commented Nov 1, 2022

Looking here (for Java 8, but newer also?), it says:

If a proxy class implements a non-public interface, then it will be defined in the same package as that interface. Otherwise, the package of a proxy class is also unspecified. Note that package sealing will not prevent a proxy class from being successfully defined in a particular package at runtime, and neither will classes already defined in the same class loader and the same package with particular signers.

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!

@galderz
Copy link

galderz commented Nov 2, 2022

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:

Invoked method: put
class jdk.proxy1.$Proxy0

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.

@ppalaga
Copy link
Contributor Author

ppalaga commented Nov 2, 2022

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 register(InitTime initTime, Class<?>... interfaces)

@ppalaga
Copy link
Contributor Author

ppalaga commented Nov 3, 2022

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 register(InitTime initTime, Class<?>... interfaces)

Any opinion on that @galderz @zakkak ?

@ppalaga
Copy link
Contributor Author

ppalaga commented Nov 3, 2022

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.

@galderz
Copy link

galderz commented Nov 3, 2022

Any opinion on that @galderz @zakkak ?

To reiterate my reaction to the first time you wrote it 👉 👀

@ppalaga
Copy link
Contributor Author

ppalaga commented Nov 4, 2022

Any opinion on that @galderz @zakkak ?

To reiterate my reaction to the first time you wrote it point_right eyes

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?

@galderz
Copy link

galderz commented Nov 4, 2022

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.

@galderz
Copy link

galderz commented Nov 4, 2022

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?

Did you try making ImageService, ImageData and/or any other classes that add the java.awt dependency to be runtime initialized? Unless I'm mistaken, a proxy's input is really the interface(s) that it implements, so if you have an issue the cause is likely in one of the interfaces, or classes they depend on.

@ppalaga
Copy link
Contributor Author

ppalaga commented Mar 16, 2023

Did you try making ImageService, ImageData and/or any other classes that add the java.awt dependency to be runtime initialized? Unless I'm mistaken, a proxy's input is really the interface(s) that it implements, so if you have an issue the cause is likely in one of the interfaces, or classes they depend on.

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:

  • The end user's service endpoint interface, here ClientWithRuntimeInitializedPayload
  • jakarta.xml.ws.BindingProvider
  • java.io.Closeable
  • org.apache.cxf.endpoint.Client

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.

ppalaga added a commit to ppalaga/quarkus-cxf that referenced this issue Mar 16, 2023
…ned initialization in their method signatures cannot be compiled to native
ppalaga added a commit to ppalaga/quarkus-cxf that referenced this issue Mar 16, 2023
…itialization in their method signatures cannot be compiled to native
@ppalaga
Copy link
Contributor Author

ppalaga commented Mar 16, 2023

org.apache.cxf.endpoint.Client fixes also the original test with AWT image: ppalaga@i580-manual-fix-mtom-awt

@ppalaga
Copy link
Contributor Author

ppalaga commented Mar 16, 2023

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.

@zakkak
Copy link

zakkak commented Mar 17, 2023

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.

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?

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.

Correct, I don't think this is a big issue and I think it would be easy to fix even if it happened.

@ppalaga
Copy link
Contributor Author

ppalaga commented Mar 21, 2023

Are these client proxies on the critical path?

Yes, in SOAP client apps, every request goes through them.

ppalaga added a commit to ppalaga/quarkus-cxf that referenced this issue Mar 23, 2023
…ned initialization in their method signatures cannot be compiled to native
ppalaga added a commit to ppalaga/quarkus-cxf that referenced this issue Mar 23, 2023
ppalaga added a commit to ppalaga/quarkus-cxf that referenced this issue Mar 23, 2023
@zakkak
Copy link

zakkak commented Mar 23, 2023

Yes, in SOAP client apps, every request goes through them.

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:

java.awt.Image the class was requested to be initialized at run time (Required for sun.text.bidi.BidiBase.NumericShapings). jdk.proxy4.$Proxy131 caused initialization of this class with the following trace

Checking sun.text.bidi.BidiBase.NumericShapings I see that it tries to load java.awt so I would start by requesting sun.text.bidi.BidiBase.NumericShapings instead of org.apache.cxf.endpoint.Client to be initialized at runtime.

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).

ppalaga added a commit that referenced this issue Mar 23, 2023
…ization in their method signatures cannot be compiled to native
ppalaga added a commit that referenced this issue Mar 23, 2023
@ppalaga
Copy link
Contributor Author

ppalaga commented Mar 28, 2023

@zakkak thanks for looking into this again!

Checking sun.text.bidi.BidiBase.NumericShapings I see that it tries to load java.awt so I would start by requesting sun.text.bidi.BidiBase.NumericShapings instead of org.apache.cxf.endpoint.Client to be initialized at runtime.

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.

@zakkak
Copy link

zakkak commented Mar 28, 2023

But this issue can occur for any runtime initialized class, not only Image and so I'd prefer some general solution.

True, it's another tradeoff between fine control of what gets runtime initialized and what not I guess.

ppalaga added a commit to ppalaga/quarkus-cxf that referenced this issue Apr 12, 2023
ppalaga added a commit that referenced this issue Apr 12, 2023
ppalaga added a commit to ppalaga/quarkus-cxf that referenced this issue Apr 30, 2023
…ned initialization in their method signatures cannot be compiled to native
ppalaga added a commit that referenced this issue Apr 30, 2023
…ization in their method signatures cannot be compiled to native
@ppalaga ppalaga added this to the 2.0.0 milestone Sep 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment