I had an issue with Groovy and GraalVM native-image that occurs rarely but 
consistently.
It is especially problematic because it happens only at runtime without any 
warning from
the native-image builder.

I cannot claim I understand all underlying mechanics precisely, but I will try 
to explain it
according to my understanding.

Since I spent some time fighting with this thing, I thought it might be helpful 
for others to
know about it and maybe even mention the issue in Groovy documentation 
somewhere.
I did not find any mention of a similar problem online.

So, back to the problem. In the native image built with GraalVM, when executing 
some
methods repeatedly for a huge number of times (more than 10000 to be exact), 
Groovy
can cause the following exception:

  com.oracle.svm.core.jdk.UnsupportedFeatureError: Unsupported method 
java.lang.invoke.MethodHandleNatives.setCallSiteTargetNormal(CallSite, 
MethodHandle) is reachable

The exception is caused by

  
org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:315)

method. Here is an example of a full stack trace:

11:41:22.349 [main] INFO o.k.t.g.s.r.r.GradleSourceRepackager - Repackaging 
Gradle sources: 
/Users/dmurat/work/dev/croz/klokwrk/klokwrk-project/modules/other/tool/klokwrk-tool-gradle-source-repack/gradle-8.5-all.zip
 ===> 
/Users/dmurat/.gradle/caches/8.5/generated-gradle-jars/gradle-api-8.5-sources.jar
Exception in thread "main" com.oracle.svm.core.jdk.UnsupportedFeatureError: 
Unsupported method 
java.lang.invoke.MethodHandleNatives.setCallSiteTargetNormal(CallSite, 
MethodHandle) is reachable
 at 
org.graalvm.nativeimage.builder/com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:121)
 at 
java.base@21.0.1/java.lang.invoke.MethodHandleNatives.setCallSiteTargetNormal(MethodHandleNatives.java)
 at 
java.base@21.0.1/java.lang.invoke.CallSite.setTargetNormal(CallSite.java:291)
 at 
java.base@21.0.1/java.lang.invoke.MutableCallSite.setTarget(MutableCallSite.java:155)
 at 
org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:315)
 at 
java.base@21.0.1/java.lang.invoke.LambdaForm$DMH/s7887ae77.invokeStaticInit(LambdaForm$DMH)
 at 
org.klokwrk.tool.gradle.source.repack.repackager.GradleSourceRepackagerInfo.getGradleDistributionSrcDirPath(GradleSourceRepackagerInfo.groovy:61)
 at 
org.klokwrk.tool.gradle.source.repack.repackager.GradleSourceRepackager$_calculateCountOfTargetZipEntries_closure1$_closure4.doCall(GradleSourceRepackager.groovy:62)
 at java.base@21.0.1/java.lang.reflect.Method.invoke(Method.java:580)
 at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:343)
 at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:328)
 at 
org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:279)
 at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1008)
 at groovy.lang.Closure.call(Closure.java:433)
 at 
org.codehaus.groovy.runtime.ConvertedClosure.invokeCustom(ConvertedClosure.java:52)
 at 
org.codehaus.groovy.runtime.ConversionHandler.invoke(ConversionHandler.java:113)
 at jdk.proxy1/jdk.proxy1.$Proxy50.test(Unknown Source)
 at 
java.base@21.0.1/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:178)
 at 
java.base@21.0.1/java.util.zip.ZipFile$EntrySpliterator.tryAdvance(ZipFile.java:573)
 at 
java.base@21.0.1/java.util.Spliterator.forEachRemaining(Spliterator.java:332)
 at 
java.base@21.0.1/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
 at 
java.base@21.0.1/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
 at 
java.base@21.0.1/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
 at 
java.base@21.0.1/java.util.stream.ReduceOps$5.evaluateSequential(ReduceOps.java:258)
 at 
java.base@21.0.1/java.util.stream.ReduceOps$5.evaluateSequential(ReduceOps.java:248)
 at 
java.base@21.0.1/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
 at 
java.base@21.0.1/java.util.stream.ReferencePipeline.count(ReferencePipeline.java:709)
 at 
org.klokwrk.tool.gradle.source.repack.repackager.GradleSourceRepackager$_calculateCountOfTargetZipEntries_closure1.doCall(GradleSourceRepackager.groovy:61)
 at java.base@21.0.1/java.lang.reflect.Method.invoke(Method.java:580)
 at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:343)
 at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:328)
 at 
org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:279)
 at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1008)
 at groovy.lang.Closure.call(Closure.java:433)
 at groovy.lang.Closure.call(Closure.java:422)
 at 
org.codehaus.groovy.runtime.IOGroovyMethods.withCloseable(IOGroovyMethods.java:1616)
 at 
org.klokwrk.tool.gradle.source.repack.repackager.GradleSourceRepackager.calculateCountOfTargetZipEntries(GradleSourceRepackager.groovy:59)
 at 
org.klokwrk.tool.gradle.source.repack.repackager.GradleSourceRepackager.repackGradleSource(GradleSourceRepackager.groovy:53)
 at 
org.klokwrk.tool.gradle.source.repack.GradleSourceRepackCommand.run(GradleSourceRepackCommand.groovy:130)
 at picocli.CommandLine.executeUserObject(CommandLine.java:2026)
 at picocli.CommandLine.access$1500(CommandLine.java:148)
 at 
picocli.CommandLine$RunLast.executeUserObjectOfLastSubcommandWithSameParent(CommandLine.java:2461)
 at picocli.CommandLine$RunLast.handle(CommandLine.java:2453)
 at picocli.CommandLine$RunLast.handle(CommandLine.java:2415)
 at 
picocli.CommandLine$AbstractParseResultHandler.execute(CommandLine.java:2273)
 at picocli.CommandLine$RunLast.execute(CommandLine.java:2417)
 at picocli.CommandLine.execute(CommandLine.java:2170)
 at io.micronaut.configuration.picocli.PicocliRunner.run(PicocliRunner.java:137)
 at io.micronaut.configuration.picocli.PicocliRunner.run(PicocliRunner.java:114)
 at 
org.klokwrk.tool.gradle.source.repack.GradleSourceRepackCommand.main(GradleSourceRepackCommand.groovy:64)


When looking at the source, the corresponding "if" branch is triggered, besides 
other things,
because "INDY_OPTIMIZE_THRESHOLD" is exceeded. After looking at the definition 
of the
constant, it is visible that it is read from the 
"groovy.indy.optimize.threshold" system property
and has a default of 10_000.

I tried to change the value to 100_000 by passing the property to the native 
executable, but
that does not work, unfortunately. The property must be set during native image 
building to
become effective. I've used the following options with native-builder:

 "-J-Dgroovy.indy.optimize.threshold=100000",
 "-J-Dgroovy.indy.fallback.threshold=100000"

As can be seen, besides "groovy.indy.optimize.threshold," there is also a 
related "groovy.indy.fallback.threshold"
system property set. After those changes, the created native image no longer 
throws the
exception mentioned above.

I hope this description will be helpful to anyone encountering similar issues.

Best regards,
Damir Murat

Reply via email to