I’d like to formulate a plan for having Akka offer out of the box support for Graal’s native-image tool. I fear that Akka will be left behind as other toolkits and frameworks gain such support.
Native image generation is not only important for fast startup, but also for reduced memory consumption. My tests of Graal’s native image tooling have shown a simple hello world app reduces its resident memory size by a factor of 10. Reducing resident memory has a positive impact on a business given reduced operating costs i.e. there are less machines required to run a system.
Outside of the JVM, using Graal’s native image tool can present Akka based services that compete with Go on all fronts. Go is fast becoming an alternative to using the JVM for microservices, particularly because of its focus on resource usage.
My initial experiments with Graal and Akka resulted in finding a Graal issue in rc1 (1). The Graal team haven’t been focused on Akka and I think that it is up to us, the Akka community, to bring Akka to their attention.
I’ve previously attempted to raise the subject of reduced resident memory usage for Akka, and got a lukewarm response (2). My former colleagues at Lightbend may recall me harping on the importance of addressing JVM resident memory usage over the years. My Landlord project was even formed to tackle this problem head on (3). Now that Graal is real, I’d like to test the water again and gauge whether any efforts in formulating a plan to tackle Akka and Graal’s native image support would be warmly received.
Thanks for reading this far. In summary, is there an interest in a pursuit of Akka for Graal’s native image tool?
I have been playing around with GraalVM native image generator and Akka applications on my free time as well. I got stuck when it came to running the generated image as the classloader is not available then.
It sounds like Dynamic Class Loading - if so, then would this mean that Akka may never be suitable for native-image making given its use of classloading?
As said a few times already; there is nothing core in Akka that “really relies on and needs” dynamic classloading. The mailboxes etc are nowadays found like this but it’s not a core thing and could be made to not use reflection. Please open specific tickets about things you find.
Sure, and I’m requesting a ticket to be opened about this. If it’s just the mailboxes a simple switch or flag could solve it - by far no “may never be suitable” as to phrased it, which could cause people to have a wrong impression.
All such classloading is done via DynamicAccess and the default implementation ReflectiveDynamicAccess. It is currently not possible to replace the implementation of DynamicAccess but we could easily support that via ActorSystemSetup parameters so that an application/library could provide it’s own custom DynamicAccess implementation that is creating requested instances or classes explicitly instead.
The issue I bumped into (note that I did a very basic smoke-test) was that classloader was not available. Both classOf[MyClass].getClassLoader and Thread.currentThread.getContextClassLoader returned null. And therefore loading the config in the Typesafe Config library failed.
Since that is not listed in the SubstrateVM limitations, maybe that is a bug in SubtrateVM itself.
GraalVM developer here. As mentioned in the limitations document, dynamically loading classes cannot be supported on SubstrateVM. However, that doesn’t mean that we cannot have ClassLoader objects in the image, especially since the ClassLoader is also used to load resources. This is being currently worked on and classOf[MyClass].getClassLoader and Thread.currentThread.getContextClassLoader should work in the near future. Of course for classOf[MyClass].getClassLoader to work the class needs to be visible during native image building and properly registered for reflection. We are also working on automatically detecting patterns like Class.forName("x.y.z.MyClass") and register referenced classes for reflection as long as the class name is a constant or it can be constant folded.
Actually the problem of running akka on GraalVM is actually the reference.conf and application.conf files (Lightbend Config. These can’t be embedded in the native-image. (only Resource Bundles are supported, but sadly both are not resource bundles. (in some way
Even when they could be embedded, it would not work, because lightbend config uses a ClassLoader to load these resource files. (And I guess akka actually calls ConfigFactory.defaultReference which means that it always tries to load the reference.conf from the ClassPath)
@schmitch I don’t think that this is true. The native-image option is called -H:IncludeResources (was quite hard to find it). It allows to include any resource files from the classpath (based on the regexp) in the native image. Recently I used it in a small project.
Edit: sadly Play/Akka uses ClassLoader.getResource (Typesafe Config) which does not work. (Class.getResource works…).
However while working again on Play and Graal if could get it to create an image that fails on runtime with:
Caused by: Configuration error: Configuration error[Context class loader is not set for the current thread; if Thread.currentThread().getContextClassLoader() returns null, you must pass a ClassLoader explicitly to ConfigFactory.defaultApplication]
This change set improves native-image reflection support by automatically detecting reflective calls and intrinsifying them to the corresponding target elements statically. Please read the updated REFLECTION.md for details.
I played around with this a bit this evening, using a small ping/pong project and subclassing and overriding ActorSystemImpl#createDynamicAccess to statically instantiate everything needed by Akka, mailboxes, etc.
I then used the following command to create a native image with the necessary .conf files:
The strategy seems like it will work when dealing with the reflection-related issues, but then I hit the next roadblock which seems to be usage of Unsafe, note in particular the error from native-image below:
~/work/garbage/akka-ping-pong/target#master $ native-image '-H:IncludeResources=^([^.]+\.conf)$' -jar scala-2.12/akka-ping-pong-assembly-0.1.0-SNAPSHOT.jar
Build on Server(pid: 633, port: 37253)
[akka-ping-pong-assembly-0.1.0-SNAPSHOT:633] classlist: 3,489.71 ms
[akka-ping-pong-assembly-0.1.0-SNAPSHOT:633] (cap): 577.37 ms
[akka-ping-pong-assembly-0.1.0-SNAPSHOT:633] setup: 953.42 ms
Warning: RecomputeFieldValue.FieldOffset automatic substitution failed. The automatic substitution registration was attempted because a call to sun.misc.Unsafe.objectFieldOffset(Field) was detected in the static initializer of akka.actor.LightArrayRevolverScheduler$. Detailed failure reason(s): The field akka.actor.LightArrayRevolverScheduler$.akka$actor$LightArrayRevolverScheduler$$taskOffset, where the value produced by the field offset computation is stored, is not static.
[akka-ping-pong-assembly-0.1.0-SNAPSHOT:633] analysis: 2,263.75 ms
error: Error encountered while parsing akka.actor.LightArrayRevolverScheduler$TaskHolder.extractTask(java.lang.Runnable)
Parsing context:
parsing akka.actor.LightArrayRevolverScheduler$TaskHolder.cancel(LightArrayRevolverScheduler.scala:339)
parsing akka.actor.LightArrayRevolverScheduler.akka$actor$LightArrayRevolverScheduler$$schedule(LightArrayRevolverScheduler.scala:173)
parsing akka.actor.LightArrayRevolverScheduler.scheduleOnce(LightArrayRevolverScheduler.scala:134)
parsing akka.dispatch.MessageDispatcher.akka$dispatch$MessageDispatcher$$scheduleShutdownAction(AbstractDispatcher.scala:174)
parsing akka.dispatch.MessageDispatcher$$anon$3.run(AbstractDispatcher.scala:224)
parsing java.lang.Shutdown.runHooks(Shutdown.java:123)
parsing java.lang.Shutdown.sequence(Shutdown.java:167)
parsing java.lang.Shutdown.shutdown(Shutdown.java:234)
parsing com.oracle.svm.core.jdk.RuntimeSupport.shutdown(RuntimeSupport.java:179)
parsing com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:189)
parsing com.oracle.svm.core.code.CEntryPointCallStubs.com_002eoracle_002esvm_002ecore_002eJavaMainWrapper_002erun_0028int_002corg_002egraalvm_002enativeimage_002ec_002etype_002eCCharPointerPointer_0029(generated:0)
Original error: com.oracle.svm.hosted.analysis.flow.SVMMethodTypeFlowBuilder$UnsafeOffsetError: Field AnalysisField<LightArrayRevolverScheduler$.akka$actor$LightArrayRevolverScheduler$$taskOffset accessed: false reads: true written: false> is used as an offset in an unsafe operation, but no value recomputation found.
Wrapped field: HotSpotField<akka.actor.LightArrayRevolverScheduler$.akka$actor$LightArrayRevolverScheduler$$taskOffset long:16>
at com.oracle.svm.hosted.analysis.flow.SVMMethodTypeFlowBuilder$UnsafeOffsetError.report(SVMMethodTypeFlowBuilder.java:118)
at com.oracle.svm.hosted.analysis.flow.SVMMethodTypeFlowBuilder.checkUnsafeOffset(SVMMethodTypeFlowBuilder.java:153)
at com.oracle.graal.pointsto.flow.MethodTypeFlowBuilder$NodeIterator.node(MethodTypeFlowBuilder.java:1079)
at org.graalvm.compiler.phases.graph.PostOrderNodeIterator.apply(PostOrderNodeIterator.java:106)
at com.oracle.graal.pointsto.flow.MethodTypeFlowBuilder.apply(MethodTypeFlowBuilder.java:404)
at com.oracle.graal.pointsto.flow.MethodTypeFlow.doParse(MethodTypeFlow.java:310)
at com.oracle.graal.pointsto.flow.MethodTypeFlow.ensureParsed(MethodTypeFlow.java:300)
at com.oracle.graal.pointsto.flow.MethodTypeFlow.addContext(MethodTypeFlow.java:107)
at com.oracle.graal.pointsto.flow.SpecialInvokeTypeFlow.onObservedUpdate(InvokeTypeFlow.java:421)
at com.oracle.graal.pointsto.flow.TypeFlow.notifyObservers(TypeFlow.java:347)
at com.oracle.graal.pointsto.flow.TypeFlow.update(TypeFlow.java:389)
at com.oracle.graal.pointsto.BigBang$2.run(BigBang.java:508)
at com.oracle.graal.pointsto.util.CompletionExecutor.lambda$execute$0(CompletionExecutor.java:174)
at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1402)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Error: Processing image build request failed