Pi4J / pi4j-v2

Pi4J Version 2.0
Apache License 2.0
273 stars 57 forks source link

graalvm java native image fails due to randomness in loading the proxies #223

Closed ganeshs4 closed 2 weeks ago

ganeshs4 commented 2 years ago

I have been trying to generate java native image (graalvm) for my home automation project which uses pi4j library (it's an amazing library for raspberry pi!).

The native-image is created without any issues. However, when trying to run the native-image, I ran into below issue:

Exception in thread "main" com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface com.pi4j.common.Identity, interface com.pi4j.common.Lifecycle, interface com.pi4j.plugin.raspberrypi.provider.spi.RpiSpiProvider, interface com.pi4j.common.Describable, interface com.pi4j.extension.Extension, interface com.pi4j.io.spi.SpiProvider, interface com.pi4j.provider.Provider] not found. Generating proxy classes at runtime is not supported. Proxy classes need to be defined at image build time by specifying the list of interfaces that they implement. To define proxy classes use -H:DynamicProxyConfigurationFiles=<comma-separated-config-files> and -H:DynamicProxyConfigurationResources=<comma-separated-config-resources> options.
    at com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:89)
    at com.oracle.svm.reflect.proxy.DynamicProxySupport.getProxyClass(DynamicProxySupport.java:158)
    at java.lang.reflect.Proxy.getProxyConstructor(Proxy.java:48)
    at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:1037)
    at com.pi4j.provider.impl.DefaultRuntimeProviders.add(DefaultRuntimeProviders.java:267)
    at com.pi4j.provider.impl.DefaultRuntimeProviders.add(DefaultRuntimeProviders.java:242)
    at com.pi4j.provider.impl.DefaultRuntimeProviders.initialize(DefaultRuntimeProviders.java:373)
    at com.pi4j.runtime.impl.DefaultRuntime.initialize(DefaultRuntime.java:260)
    at com.pi4j.context.impl.DefaultContext.<init>(DefaultContext.java:103)
    at com.pi4j.context.impl.DefaultContext.newInstance(DefaultContext.java:72)
    at com.pi4j.context.impl.DefaultContextBuilder.build(DefaultContextBuilder.java:277)
    at com.pi4j.context.impl.DefaultContextBuilder.build(DefaultContextBuilder.java:48)
    at com.pi4j.Pi4J.newAutoContext(Pi4J.java:71)

Steps to recreate the issue:

Step 1: Download and unzip graalvm CE on ARM64 (raspberry pi) Step 2: Run the below command to generate the native image

/home/rpi/graalvm-ce-java17-22.1.0/bin/native-image --no-fallback -H:DynamicProxyConfigurationFiles=/home/rpi/deps/proxy-config.json -jar /home/rpi/testApp.jar testApplication

Step 3: Contents of /home/rpi/deps/proxy-config.json

[
  {
    "interfaces":["com.pi4j.common.Describable","com.pi4j.provider.Provider","com.pi4j.extension.Extension","com.pi4j.common.Identity","com.pi4j.io.spi.SpiProvider","com.pi4j.plugin.raspberrypi.provider.spi.RpiSpiProvider","com.pi4j.common.Lifecycle"]}
  ,
  {
    "interfaces":["com.pi4j.common.Describable","com.pi4j.provider.Provider","com.pi4j.extension.Extension","com.pi4j.common.Identity","com.pi4j.plugin.raspberrypi.provider.serial.RpiSerialProvider","com.pi4j.io.serial.SerialProvider","com.pi4j.common.Lifecycle"]}
  ,
  {
    "interfaces":["com.pi4j.io.gpio.digital.DigitalOutputProvider","com.pi4j.io.gpio.GpioProvider","com.pi4j.common.Describable","com.pi4j.io.gpio.digital.DigitalProvider","com.pi4j.provider.Provider","com.pi4j.extension.Extension","com.pi4j.common.Identity","com.pi4j.plugin.raspberrypi.provider.gpio.digital.RpiDigitalOutputProvider","com.pi4j.common.Lifecycle"]}
  ,
  {
    "interfaces":["com.pi4j.io.i2c.I2CProvider","com.pi4j.common.Describable","com.pi4j.provider.Provider","com.pi4j.extension.Extension","com.pi4j.plugin.raspberrypi.provider.i2c.RpiI2CProvider","com.pi4j.common.Identity","com.pi4j.common.Lifecycle"]}
  ,
  {
    "interfaces":["com.pi4j.io.pwm.PwmProvider","com.pi4j.common.Describable","com.pi4j.provider.Provider","com.pi4j.extension.Extension","com.pi4j.common.Identity","com.pi4j.plugin.raspberrypi.provider.pwm.RpiPwmProvider","com.pi4j.common.Lifecycle"]}
  ,
  {
    "interfaces":["com.pi4j.plugin.raspberrypi.provider.gpio.digital.RpiDigitalInputProvider","com.pi4j.io.gpio.GpioProvider","com.pi4j.common.Describable","com.pi4j.io.gpio.digital.DigitalProvider","com.pi4j.provider.Provider","com.pi4j.extension.Extension","com.pi4j.common.Identity","com.pi4j.io.gpio.digital.DigitalInputProvider","com.pi4j.common.Lifecycle"]} 
]

I checked with the graalvm community (thanks to Vojin Jovanovic and Aleksandar Gradinac) and seems like the reason the native image is failing with the above exception is because - the order of proxy keeps changing and if the order doesn't match we will see above exception when trying to execute the native image. They recommend adding the proxies in the same order every time. Below are the snippets from my conversation with Aleksandar Gradinac - explaining what is going wrong:

one note regarding proxies: the order of interfaces matter, two proxies that specify the same interfaces but in different order result in two separate classes under the hood.
....
.
pi4j library could be iterating over a collection with an unstable iteration order in the library

Their recommendation was:

 tweak the library(pi4j) itself and remove the randomness of what they pass to the `newProxyInstance` call
the first randomness i see here is this HashSet - it has an undefined iteration order - https://github.com/Pi4J/pi4j-v2/blob/2.0/pi4j-core/src/main/java/com/pi4j/runtime/impl/DefaultRuntime.java#L213
gradinac commented 2 years ago

One note: the above problem happens on the 2.0 release of the library, it seems like this particular issue has been fixed on the develop branch - https://github.com/Pi4J/pi4j-v2/issues/141

eitch commented 2 weeks ago

The proxies have been removed, so this shouldn't be an issue anymore.