micronaut-projects / micronaut-maven-plugin

Maven plugin to execute Micronaut applications
https://micronaut-projects.github.io/micronaut-maven-plugin/latest/
Apache License 2.0
21 stars 22 forks source link

Micronaut cannot load Truffle/polyglot languages within the shaded jar. #866

Open chumer opened 1 year ago

chumer commented 1 year ago

Expected Behavior

With GraalVM 23.1 (GraalVM for JDK 21) GraalVM Truffle/Polyglot languages are now consumed from the class/module-path. For context, this project is also known as Truffle Unchained, and we wrote up all about it here: https://medium.com/graalvm/truffle-unchained-13887b77b62c

For that to work, we require the optimizing Truffle runtime to run as a named module. The reason is that we would otherwise need to export the internal JVMCI API to ALL-UNNAMED. The JVMCI is unsafe on steroids and must not be exported to ALL-UNNAMED to preserve the VM integrity.

There is no problem with regular Java SE applications that leverage the module-path or use a module class loader as the truffle runtime runs as named module. As far as I understand Micronaut cannot run modules as named modules atm, therefore this solution cannot be applied. But please correct me if I am wrong.

When running languages from the classpath the Polyglot runtime automatically scans the classpath and looks for modular jar files that it can spawn on a separate module-layer + classloader. This approach typically works for regular Java SE applications, but it fails when Micronaut shades everything into a single jar file.

As a workaround, if you run with mvn mn:run and it works because it uses the regular class-path.

Another workaround I had to deploy to make micronaut work with GraalVM for JDK 21 was I had to add these two dependencies:

    <dependency>
      <groupId>org.graalvm.sdk</groupId>
      <artifactId>graal-sdk</artifactId>
      <version>23.1.0</version>
    </dependency>
    <dependency>
      <groupId>org.graalvm.truffle</groupId>
      <artifactId>truffle-api</artifactId>
      <version>23.1.0</version>
    </dependency>

The reason for this workaround is that the micronaut plugin depends on the two dependencies with version 23.0.0 which causes issues.

I've discussed this briefly with @sdelamo at the GraalVM Meetup and he asked me to open an issue to start the discussion. I do not think shading all the language jars into a single jar can work because this makes it impossible to run jars as named modules. Are there already plans to revisit this? Do you have alternative ways to deploy that we could document?

Other frameworks like Spring and Quarkus use custom class loaders, but they keep the jar files intact, so there in theory we enumerate jar files for the module-layer with an integration API. I do not see how this can work with shading.

Quarkus issue: https://github.com/quarkusio/quarkus/issues/36242

I've discussed this briefly with @sdelamo at the GraalVM Meetup and he asked me to open an issue to start the discussion.

Actual Behaviour

~/g/1/f/embedding-micronaut $ $JAVA_HOME/bin/java -jar ./target/micronautguide-0.1.jar
 __  __ _                                  _
|  \/  (_) ___ _ __ ___  _ __   __ _ _   _| |_
| |\/| | |/ __| '__/ _ \| '_ \ / _` | | | | __|
| |  | | | (__| | | (_) | | | | (_| | |_| | |_
|_|  |_|_|\___|_|  \___/|_| |_|\__,_|\__,_|\__|
15:28:27.082 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 359ms. Server Running: http://localhost:8080
15:28:29.766 [default-nioEventLoopGroup-1-2] ERROR i.m.http.server.RouteExecutor - Unexpected error occurred: No language and polyglot implementation was found on the class-path. Make sure at last one language is added on the class-path. If you put a language on the class-path and you encounter this error then there could be a problem with isolated class loading. Use -Dpolyglotimpl.TraceClassPathIsolation=true to debug class loader islation problems. For best performance it is recommended to use polyglot from the module-path instead of the class-path.
java.lang.IllegalStateException: No language and polyglot implementation was found on the class-path. Make sure at last one language is added on the class-path. If you put a language on the class-path and you encounter this error then there could be a problem with isolated class loading. Use -Dpolyglotimpl.TraceClassPathIsolation=true to debug class loader islation problems. For best performance it is recommended to use polyglot from the module-path instead of the class-path.
    at org.graalvm.polyglot.Engine$PolyglotInvalid.noPolyglotImplementationFound(Engine.java:2071)
    at org.graalvm.polyglot.Engine$PolyglotInvalid.createHostAccess(Engine.java:2057)
    at org.graalvm.polyglot.Engine$PolyglotInvalid.createHostAccess(Engine.java:2023)
    at org.graalvm.polyglot.Engine$Builder.build(Engine.java:753)
    at org.graalvm.polyglot.Context$Builder.build(Context.java:1925)
    at example.micronaut.Application.index(Application.java:38)
    at example.micronaut.$Application$Definition$Exec.dispatch(Unknown Source)
    at io.micronaut.context.AbstractExecutableMethodsDefinition$DispatchedExecutableMethod.invoke(AbstractExecutableMethodsDefinition.java:442)
    at io.micronaut.context.DefaultBeanContext$BeanContextExecutionHandle.invoke(DefaultBeanContext.java:4256)
    at io.micronaut.web.router.AbstractRouteMatch.execute(AbstractRouteMatch.java:223)
    at io.micronaut.http.context.ServerRequestContext.with(ServerRequestContext.java:74)
    at io.micronaut.http.server.RouteExecutor.executeRouteAndConvertBody(RouteExecutor.java:480)
    at io.micronaut.http.server.RouteExecutor.callRoute(RouteExecutor.java:470)
    at io.micronaut.http.server.RequestLifecycle.lambda$normalFlow$2(RequestLifecycle.java:146)
    at io.micronaut.core.execution.ImperativeExecutionFlowImpl.flatMap(ImperativeExecutionFlowImpl.java:72)
    at io.micronaut.http.server.RequestLifecycle.lambda$normalFlow$4(RequestLifecycle.java:146)
    at io.micronaut.http.server.RequestLifecycle.lambda$runWithFilters$14(RequestLifecycle.java:264)
    at io.micronaut.http.filter.FilterRunner.processRequestFilter(FilterRunner.java:308)
    at io.micronaut.http.filter.FilterRunner.filterRequest0(FilterRunner.java:183)
    at io.micronaut.http.filter.FilterRunner.lambda$filterRequest0$3(FilterRunner.java:183)
    at io.micronaut.http.filter.FilterRunner.processRequestFilter(FilterRunner.java:242)
    at io.micronaut.http.filter.FilterRunner.filterRequest0(FilterRunner.java:183)
    at io.micronaut.http.filter.FilterRunner.lambda$filterRequest0$3(FilterRunner.java:183)
    at io.micronaut.core.execution.ImperativeExecutionFlowImpl.flatMap(ImperativeExecutionFlowImpl.java:72)
    at io.micronaut.http.filter.FilterRunner.processRequestFilter(FilterRunner.java:272)
    at io.micronaut.http.filter.FilterRunner.filterRequest0(FilterRunner.java:183)
    at io.micronaut.http.filter.FilterRunner.filterRequest(FilterRunner.java:167)
    at io.micronaut.http.filter.FilterRunner.run(FilterRunner.java:162)
    at io.micronaut.http.server.RequestLifecycle.runWithFilters(RequestLifecycle.java:281)
    at io.micronaut.http.server.RequestLifecycle.normalFlow(RequestLifecycle.java:143)
    at io.micronaut.http.server.netty.NettyRequestLifecycle.handleNormal(NettyRequestLifecycle.java:85)
    at io.micronaut.http.server.netty.RoutingInBoundHandler.accept(RoutingInBoundHandler.java:220)
    at io.micronaut.http.server.netty.handler.PipeliningServerHandler$MessageInboundHandler.read(PipeliningServerHandler.java:357)
    at io.micronaut.http.server.netty.handler.PipeliningServerHandler.channelRead(PipeliningServerHandler.java:206)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
    at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
    at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
    at io.netty.handler.codec.MessageToMessageCodec.channelRead(MessageToMessageCodec.java:111)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
    at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:93)
    at io.netty.handler.codec.http.HttpServerKeepAliveHandler.channelRead(HttpServerKeepAliveHandler.java:64)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
    at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
    at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318)
    at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
    at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
    at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.base/java.lang.Thread.run(Thread.java:1583)

Steps To Reproduce

  1. Download and unpack: embedding-micronaut.zip
  2. Download GraalVM for JDK 21 and set your JAVA_HOME to the installation.
  3. Run mvn package in the embedding-micronaut folder
  4. Run $JAVA_HOME/bin/java -jar ./target/micronautguide-0.1.jar

Environment Information

GraalVM for JDK 21 Polyglot version 23.1.0

Example Application

No response

Version

4.0.3

graemerocher commented 1 year ago

probably works if you exclude shading and/or use docker image packaging

chumer commented 1 year ago

@graemerocher can you please point me to docs I can link from our docs?

graemerocher commented 1 year ago

@chumer think you need to configure true for the shade plugin in maven https://maven.apache.org/plugins/maven-shade-plugin/shade-mojo.html#skip

@alvarosanchez think we need an example in the docs

chumer commented 1 year ago

Yes.

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-shade-plugin</artifactId>
  <version>3.5.1</version>
  <configuration>
    <skip>true</skip>
  </configuration>
</plugin>

skips shading. But when users are used to shading as their default deployment model, how will they deploy then? I fear a Micronaut user might get lost here. I guess we should offer an alternative way to build deployable artefacts, right? Or are users already expected to know what to do?

alvarosanchez commented 1 year ago

The only long-term solution is to get rid of shading, it's very problematic. It's been discussed for years, but we never get to the point to put some effort on it. Perhaps it's about time.

One thing that you should be aware thought is that the Maven Shade Plugin will keep a copy of the original JAR as original-xxx. Can you not use that?