quarkus-cxf icon indicating copy to clipboard operation
quarkus-cxf copied to clipboard

Service with `@RequestWrapper` and `@ResponseWrapper` cannot be compiled to native

Open ppalaga opened this issue 3 years ago • 3 comments

There is a reproducer in https://github.com/quarkiverse/quarkus-cxf/pull/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 avatar Oct 28 '22 15:10 ppalaga

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

ppalaga avatar Oct 28 '22 15:10 ppalaga

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 avatar Oct 29 '22 14:10 shumonsharif

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.

shumonsharif avatar Oct 29 '22 14:10 shumonsharif

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 avatar Oct 30 '22 21:10 ppalaga

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 avatar Oct 30 '22 22:10 ppalaga

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

ppalaga avatar Oct 31 '22 09:10 ppalaga

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?

zakkak avatar Oct 31 '22 09:10 zakkak

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

ppalaga avatar Oct 31 '22 10:10 ppalaga

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

zakkak avatar Oct 31 '22 10:10 zakkak

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

galderz avatar Nov 01 '22 11:11 galderz

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.

ppalaga avatar Nov 01 '22 13:11 ppalaga

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?

galderz avatar Nov 01 '22 15:11 galderz

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!

ppalaga avatar Nov 01 '22 18:11 ppalaga

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.

galderz avatar Nov 02 '22 05:11 galderz

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 avatar Nov 02 '22 09:11 ppalaga

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 avatar Nov 03 '22 08:11 ppalaga

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.

ppalaga avatar Nov 03 '22 08:11 ppalaga

Any opinion on that @galderz @zakkak ?

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

galderz avatar Nov 03 '22 09:11 galderz

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?

ppalaga avatar Nov 04 '22 11:11 ppalaga

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 avatar Nov 04 '22 13:11 galderz

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.

galderz avatar Nov 04 '22 16:11 galderz

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 https://github.com/ppalaga/quarkus-cxf/commit/69ba2bdb6591eb10047cb383f6413ee0ea13b4df :

  • The client interface has a single method Result addOperands(Operands arg0);
  • Postponed init is forced for Operands in application.properties: https://github.com/ppalaga/quarkus-cxf/commit/69ba2bdb6591eb10047cb383f6413ee0ea13b4df#diff-f3072c584ba042d6e5ce5c2eaf1e64235ed539ebed2aff95dc4e4ac78c5cff8aR28

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 avatar Mar 16 '23 16:03 ppalaga

org.apache.cxf.endpoint.Client fixes also the original test with AWT image: https://github.com/ppalaga/quarkus-cxf/commit/i580-manual-fix-mtom-awt

ppalaga avatar Mar 16 '23 18:03 ppalaga

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.

ppalaga avatar Mar 16 '23 18:03 ppalaga

Ups, #769 does not fix this. Sorry for the noise

ppalaga avatar Mar 17 '23 12:03 ppalaga

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.

zakkak avatar Mar 17 '23 14:03 zakkak

Are these client proxies on the critical path?

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

ppalaga avatar Mar 21 '23 21:03 ppalaga

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 https://github.com/oracle/graal/pull/4684 for some more info).

zakkak avatar Mar 23 '23 13:03 zakkak

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

ppalaga avatar Mar 28 '23 12:03 ppalaga

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.

zakkak avatar Mar 28 '23 13:03 zakkak