New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

akka dies with a java.lang.UnsatisfiedLinkError when run GraalVM native-image #25090

Open
codepitbull opened this Issue May 13, 2018 · 15 comments

Comments

Projects
None yet
7 participants
@codepitbull

codepitbull commented May 13, 2018

I've been trying to very a (very) small native akka-http prototype with native-image from graalvm.
The build is passing but there is an issue when running the resulting binary.

akka.util.Reflect contains the following code:

  val getCallerClass: Option[Int ⇒ Class[_]] = {
    try {
      val c = Class.forName("sun.reflect.Reflection")
      val m = c.getMethod("getCallerClass", Array(classOf[Int]): _*)
      Some((i: Int) ⇒ m.invoke(null, Array[AnyRef](i.asInstanceOf[java.lang.Integer]): _*).asInstanceOf[Class[_]])
    } catch {
      case NonFatal(e) ⇒ None
    }
  }

The selected method getCallerClass has been deprecated a while ago and is not suppoerted on Graalvm.
This causes a java.lang.UnsatisifedLinkError, bubbling right up through the Try.

This is the resulting stacktrace:

Exception in thread "main" java.lang.reflect.InvocationTargetException
	at java.lang.Throwable.<init>(Throwable.java:310)
	at java.lang.Exception.<init>(Exception.java:102)
	at java.lang.ReflectiveOperationException.<init>(ReflectiveOperationException.java:89)
	at java.lang.reflect.InvocationTargetException.<init>(InvocationTargetException.java:72)
	at com.oracle.svm.reflect.proxies.Proxy_11_QuickstartServer_main.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:199)
	at Lcom/oracle/svm/core/code/CEntryPointCallStubs;.com_002eoracle_002esvm_002ecore_002eJavaMainWrapper_002erun_0028int_002corg_002egraalvm_002enativeimage_002ec_002etype_002eCCharPointerPointer_0029(generated:0)
Caused by: java.lang.reflect.InvocationTargetException
	at java.lang.Throwable.<init>(Throwable.java:310)
	at java.lang.Exception.<init>(Exception.java:102)
	at java.lang.ReflectiveOperationException.<init>(ReflectiveOperationException.java:89)
	at java.lang.reflect.InvocationTargetException.<init>(InvocationTargetException.java:72)
	at com.oracle.svm.reflect.proxies.Proxy_12_Reflection_getCallerClass.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at akka.util.Reflect$.$anonfun$getCallerClass$1(Reflect.scala:34)
	at akka.util.Reflect$.$anonfun$getCallerClass$1$adapted(Reflect.scala:34)
	at akka.util.Reflect$$$Lambda$2807/203574664.apply(Unknown Source)
	at scala.collection.Iterator$$anon$10.next(Iterator.scala:457)
	at scala.collection.Iterator$$anon$17.hasNext(Iterator.scala:816)
	at scala.collection.Iterator$$anon$17.next(Iterator.scala:827)
	at akka.util.Reflect$.findCaller$1(Reflect.scala:164)
	at akka.util.Reflect$.$anonfun$findClassLoader$3(Reflect.scala:176)
	at akka.util.Reflect$$$Lambda$3146/578795602.apply(Unknown Source)
	at scala.Option.map(Option.scala:146)
	at akka.util.Reflect$.$anonfun$findClassLoader$2(Reflect.scala:176)
	at akka.util.Reflect$$$Lambda$3007/1594164979.apply(Unknown Source)
	at scala.Option.orElse(Option.scala:289)
	at akka.util.Reflect$.findClassLoader(Reflect.scala:176)
	at akka.actor.ActorSystem$.findClassLoader(ActorSystem.scala:382)
	at akka.actor.ActorSystem$.$anonfun$apply$2(ActorSystem.scala:242)
	at akka.actor.ActorSystem$$$Lambda$2792/1909573877.apply(Unknown Source)
	at scala.Option.getOrElse(Option.scala:121)
	at akka.actor.ActorSystem$.apply(ActorSystem.scala:242)
	at akka.actor.ActorSystem$.apply(ActorSystem.scala:289)
	at akka.actor.ActorSystem$.apply(ActorSystem.scala:234)
	at de.codepitbull.graalvm.QuickstartServer$.main(QuickstartServer.scala:16)
	at de.codepitbull.graalvm.QuickstartServer.main(QuickstartServer.scala)
	... 4 more
Caused by: java.lang.UnsatisfiedLinkError: sun.reflect.Reflection.getCallerClass(I)Ljava/lang/Class; [symbol: Java_sun_reflect_Reflection_getCallerClass or Java_sun_reflect_Reflection_getCallerClass__I]
	at java.lang.Throwable.<init>(Throwable.java:265)
	at java.lang.Error.<init>(Error.java:70)
	at java.lang.LinkageError.<init>(LinkageError.java:55)
	at java.lang.UnsatisfiedLinkError.<init>(UnsatisfiedLinkError.java:54)
	at com.oracle.svm.jni.access.JNINativeLinkage.getOrFindEntryPoint(JNINativeLinkage.java:123)
	at com.oracle.svm.jni.JNIGeneratedMethodSupport.nativeCallAddress(JNIGeneratedMethodSupport.java:52)
	at sun.reflect.Reflection.getCallerClass(Reflection.java)
	... 29 more

This happens on:

akka: 2.5.12
scala: 2.12.5
akka-http: 10.1.1

I checked the current master and the code is also there.

This is the Main-method I used (please note that using App with Graalvm doesn't work currently because of this oracle/graal#370 ):

object QuickstartServer {
  def main(args: Array[String]): Unit = {
    implicit val system: ActorSystem = ActorSystem("helloAkkaHttpServer")
    implicit val materializer: ActorMaterializer = ActorMaterializer()

    val routes: Route = pathPrefix("hello") {
      get {
        complete("World!")
      }
    }
    Http().bindAndHandle(routes, "localhost", 8080)

    println(s"Server online at http://localhost:8080/")

    Await.result(system.whenTerminated, Duration.Inf)
  }
}

And the contents of build.sbt:

lazy val akkaHttpVersion = "10.1.1"
lazy val akkaVersion    = "2.5.12"
retrieveManaged := true
lazy val root = (project in file(".")).
  settings(
    inThisBuild(List(
      organization    := "de.codepitbull.graalvm",
      scalaVersion    := "2.12.5"
    )),
    name := "graalvm-akka-http",
    libraryDependencies ++= Seq(
      "com.typesafe.akka" %% "akka-http"            % akkaHttpVersion,
      "com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion,
      "com.typesafe.akka" %% "akka-http-xml"        % akkaHttpVersion,
      "com.typesafe.akka" %% "akka-stream"          % akkaVersion,

      "com.typesafe.akka" %% "akka-http-testkit"    % akkaHttpVersion % Test,
      "com.typesafe.akka" %% "akka-testkit"         % akkaVersion     % Test,
      "com.typesafe.akka" %% "akka-stream-testkit"  % akkaVersion     % Test,
      "org.scalatest"     %% "scalatest"            % "3.0.1"         % Test
    )
  )
@ktoso

This comment has been minimized.

Member

ktoso commented May 13, 2018

Thanks for reporting, would be good to be able to have such minimal app work with graal.

To confirm, to run the reproducer it's enough to just "run with graal" on JDK10+?

// Perhaps the fix for this would be in Akka HTTP, for not using that method etc... not sure yet

@codepitbull

This comment has been minimized.

codepitbull commented May 13, 2018

Haven't tried on JDK10.
To avoid problems I build everything using JDK 8 on Mac OS X (1.8.0_152-bn16) and then use native-image from the enterprise RC available from oracle.

I use the following skript to create the binary:

sbt clean package
cp="$(find "." -name '*.jar'|tr '\n' ':')"
native-image -cp "$cp" -H:Name=akka -H:Class=de.codepitbull.graalvm.QuickstartServer  -H:+ReflectionEnabled -H:+ReportUnsupportedElementsAtRuntime -H:+JNI

This assumes native-image is in your current PATH.

@johanandren

This comment has been minimized.

Member

johanandren commented May 14, 2018

Potentially related discussion thread (esp the chime in from Codrut in the end): https://discuss.lightbend.com/t/akka-and-graal-s-native-image-tool/940/11

@codepitbull

This comment has been minimized.

codepitbull commented May 14, 2018

I remember a discussion about providing somethin akin to OSGi, whcih was answered pretty much the same way : NO.
BUT all classes Akka relies on are available at compile time. Graal basically scans ALL dependencies and is very eager with loading things for compilation. If it's on the classpath it will end up in the resulting binary.
So as long as things are part of the libraries available at compile time we should be fine.
Well, as soon as the changes mentioned in the thread get implemented in Graal.

@johanandren

This comment has been minimized.

Member

johanandren commented May 14, 2018

Or, as Patrik mentions, provide ActorSystemSetup for all the things that are currently dynamically resolved so that the default can be statically resolved instead. At least I think that would solve the problem?

@codepitbull

This comment has been minimized.

codepitbull commented May 14, 2018

Maybe this one but wouldn't it be better to get rid of the offending method?
The method is deprecated and to continue using it can cause more trouble in the future, especially since it's part of the sun-namespace.
I mean, you will need a replacement at some point.

@johanandren

This comment has been minimized.

Member

johanandren commented May 14, 2018

Hmm, after looking into it a bit I think I do agree. Reflectively accessing that internal method seems really weird to me.

Would want to know some rationale why all this trixery is at all needed in there though, why can't we either use an explicitly provided classloader (which we do support) or then just use the class loader that loaded the ActorSystem class?

@codepitbull

This comment has been minimized.

codepitbull commented May 14, 2018

Sadly there is no test (https://github.com/akka/akka/blob/563c7fbcf0fcb374d414403900d620c8fac54d52/akka-actor-tests/src/test/scala/akka/util/ReflectSpec.scala) to check what the intend behind the method is or what cases it is built to cover.
The fallbacks look pretty reasonable to me but I lack the Akka-knowledge to see in what cases the internal-method makes sense.

@johanandren

This comment has been minimized.

Member

johanandren commented May 14, 2018

Yeah, there is one PR that made it possible to replace/mock for ScalaJS (I think at least) and then I tracked back to a commit from 2012 but sadly no clear explanation.

@vhiairrassary

This comment has been minimized.

Contributor

vhiairrassary commented Jul 3, 2018

Hi! I have spent a couple of hours to test Akka (2.5-SNAPSHOT, Scala 2.12) & Graal. This is a POC and not the definitive fix/path.

  1. Clone Akka, patch it and relase a local version:
diff --git a/akka-actor/src/main/resources/reference.conf b/akka-actor/src/main/resources/reference.conf
index 5f5d841924..c65479db69 100644
--- a/akka-actor/src/main/resources/reference.conf
+++ b/akka-actor/src/main/resources/reference.conf
@@ -67,7 +67,7 @@ akka {
   #
   # Should not be set by end user applications in 'application.conf', use the extensions property for that
   #
-  library-extensions = ${?akka.library-extensions} ["akka.serialization.SerializationExtension"]
+  library-extensions = ${?akka.library-extensions} []

   # List FQCN of extensions which shall be loaded at actor system startup.
   # Should be on the format: 'extensions = ["foo", "bar"]' etc.
diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala
index 20b65221d7..855707ed15 100644
--- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala
+++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala
@@ -379,7 +379,7 @@ object ActorSystem {

   }

-  private[akka] def findClassLoader(): ClassLoader = Reflect.findClassLoader()
+  private[akka] def findClassLoader(): ClassLoader = getClass.getClassLoader
 }

 /**

Notes:

  • I know that replacing Reflect.findClassLoader() with getClass.getClassLoader is not a viable fix but it was enough for this POC
  • I was not able to support akka.serialization.SerializationExtension with the reflective system of Graal, it was possible to disable it as not used in this POC.

Then

sbt ++2.12.6 shell
project akka-actor
publishLocal
  1. Download latest GraalVM CE (1.0.0-rc3 at that time) from http://www.oracle.com/technetwork/oracle-labs/program-languages/downloads/index.html
    I used the macOS version.

  2. Compile the following demo project (https://github.com/vhiairrassary/akka_2.5-graal_1.0.0_rc3):

GRAALVM_PATH="..."

git clone git@github.com:vhiairrassary/akka_2.5-graal_1.0.0_rc3.git test-graal
cd test-graal
sbt -java-home $GRAALVM_PATH assembly

cd ./target/scala-2.12

$GRAALVM_PATH/bin/native-image \
  -jar test-graal-assembly-0.1.jar \
  -H:IncludeResources='^(application|reference|version)\.conf$' \
  -H:ReflectionConfigurationFiles=../../akka_reflection_config.json
  1. Enjoy 🎉
> ls -lh test-graal-assembly*
-rwxr-xr-x  1 vhiairrassary  staff    14M Jul  3 17:15 test-graal-assembly-0.1
-rw-r--r--  1 vhiairrassary  staff    13M Jul  3 17:13 test-graal-assembly-0.1.jar
> time java -jar ./test-graal-assembly-0.1.jar
**
-> Received: Youhou
**
java -jar ./test-graal-assembly-0.1.jar  1.46s user 0.16s system 136% cpu 1.186 total
time ./test-graal-assembly-0.1
**
-> Received: Youhou
**
./test-graal-assembly-0.1  0.01s user 0.01s system 43% cpu 0.063 total

My work is mainly a copy of the same process used for Netty: https://medium.com/graalvm/instant-netty-startup-using-graalvm-native-image-generation-ed6f14ff7692 (thanks @cstancu).

Several notes:

  • Native binary is slightly larger (14MB vs 13MB);
  • I had to create AkkaSubstitutions.java for due to an usage of Unsafe. We should either avoid to use it (I think it is deprecated) or at least fix it be automatically handled by native-image (using svm.jar is a workaround as explained in the article, and a fix in the sources is a much better fix).
  • I had to register a ton of classes (only the ones used in the POC currently) in akka_reflection_config.json. That's a point we should work on.

Hopefully someone can start from this to add support for Graal in Akka. If needed I can help (some help to start would be very appreciated due to my lack of knowledge about the internals of Akka).

@ktoso

This comment has been minimized.

Member

ktoso commented Jul 4, 2018

Very cool progress, thanks for sharing @vhiairrassary :)
To be clear though, this is about GraalVM (SubstrateVM), let's not fall into the marketing terminology – Graal itself is just a JiT that we can (anyone can) use on a normal JVM app by adding specific flags. The native image is about "GraalVM/SubstrateVM" – still, cool to see the progress!

@ktoso ktoso changed the title from akka dies with a java.lang.UnsatisfiedLinkError when run graalvm to akka dies with a java.lang.UnsatisfiedLinkError when run GraalVM native-image Jul 4, 2018

@mavilein

This comment has been minimized.

mavilein commented Sep 28, 2018

What's the current state of this issue? We are in the process of making our product compatible with Graals native-image. One of the big blockers is Akka. We would like to know whether we can expect Akka to become compatible soon or if we should start getting rid of our dependency on Akka.
Thanks a lot!

@johanandren

This comment has been minimized.

Member

johanandren commented Oct 1, 2018

The current state is what you can read in this issue. We are not currently working actively on making Akka run on SubstrateVM, so work and PRs from the community are welcome.

@sorenbs

This comment has been minimized.

sorenbs commented Oct 3, 2018

We are interested in sponsoring work on this issue. I don't know if there is a formal process for this, so feel free to reach out to me at schmidt [AT] prisma.io if anybody is interested :-)

@myfear

This comment has been minimized.

myfear commented Oct 5, 2018

@sorenbs Hi Søren! I've sent you an email a minute ago. Let's talk.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment