Skip to content
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

Native image + espresso, espressoHome not defined & internal GraalVM error #4555

Open
ac101m opened this issue May 9, 2022 · 7 comments
Open
Assignees

Comments

@ac101m
Copy link

ac101m commented May 9, 2022

Environment:

  • GraalVM version or commit id if built from source: 20.0.0.2
  • CE or EE: CE
  • JDK version: 17
  • OS and OS Version: Linux: Ubuntu 20.04
  • Architecture: amd64
  • The output of java -Xinternalversion: OpenJDK 64-Bit Server VM (17.0.2+8-jvmci-22.0-b05) for linux-amd64 JRE (17.0.2+8-jvmci-22.0-b05), built on Jan 20 2022 22:54:40 by "buildslave" with gcc 10.3.0

The error:

Exception in thread "main" org.graalvm.polyglot.PolyglotException: java.lang.NullPointerException: espressoHome not defined
	at java.util.Objects.requireNonNull(Objects.java:233)
	at com.oracle.truffle.espresso.runtime.EspressoProperties$Builder$1.<init>(EspressoProperties.java:183)
	at com.oracle.truffle.espresso.runtime.EspressoProperties$Builder.build(EspressoProperties.java:175)
	at com.oracle.truffle.espresso.runtime.EspressoContext.initVmProperties(EspressoContext.java:662)
	at com.oracle.truffle.espresso.runtime.EspressoContext.spawnVM(EspressoContext.java:484)
	at com.oracle.truffle.espresso.runtime.EspressoContext.initializeContext(EspressoContext.java:448)
	at com.oracle.truffle.espresso.EspressoLanguage.initializeContext(EspressoLanguage.java:143)
	at com.oracle.truffle.espresso.EspressoLanguage.initializeContext(EspressoLanguage.java:63)
	at com.oracle.truffle.api.TruffleLanguage$Env.postInit(TruffleLanguage.java:3317)
	at com.oracle.truffle.api.LanguageAccessor$LanguageImpl.postInitEnv(LanguageAccessor.java:289)
	at com.oracle.truffle.polyglot.PolyglotLanguageContext.ensureInitialized(PolyglotLanguageContext.java:689)
	at com.oracle.truffle.polyglot.PolyglotContextImpl.getBindings(PolyglotContextImpl.java:1043)
	at com.oracle.truffle.polyglot.PolyglotContextDispatch.getBindings(PolyglotContextDispatch.java:98)
	at org.graalvm.polyglot.Context.getBindings(Context.java:560)
	at com.r3.conclave.enclave.espressotests.Main.main(Main.java:42)
Caused by: Attached Guest Language Frames (0)
Internal GraalVM error, please report at https://github.com/oracle/graal/issues/.

Have I verified this issue still happens when using the latest snapshot?
I have not.

Code
The code consists of two parts, the host and the client.

The "host" code uses espresso and the polyglot API to load and execute a method from a jar file:

package espressohost;

import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;

public class Main {
    static final String usage = "Usage: program_name <jar_file> <main_class>";

    static Context espressoExecutionContext(String classpath) {
        String javaHome = System.getProperty("java.home");
        if (javaHome == null) {
            javaHome = System.getenv("JAVA_HOME");
        }
        if (javaHome == null) {
            System.out.println("Failed to determine java home!");
            System.exit(1);
        }
        return Context.newBuilder("java")
                .option("java.Classpath", classpath)
                .option("java.JavaHome", javaHome)
                .allowAllAccess(true)
                .build();
    }

    static public void main(String[] args) throws Exception {
        if (args.length < 2) {
            System.out.println(usage);
            System.exit(1);
        }

        final String jarFilePath = args[0];
        final String mainClassName = args[1];

        System.out.println("Jar file to load: " + jarFilePath);
        System.out.println("Main class name: " + mainClassName);

        Context polyglot = espressoExecutionContext(jarFilePath);

        Value mainClass = polyglot.getBindings("java").getMember(mainClassName);
        if (mainClass == null) {
            System.out.println("Failed to load the main class!");
            System.exit(1);
        }

        mainClass.invokeMember("main", (Object) new String[0]);
    }
}

The "client" code (for the purposes of reproducing this error) is just a trivial hello world program:

package hello;

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello world!");
    }
}

When the host code is built as a native image and executed, the aforementioned error occurs at line 42 (Value mainClass = polyglot.getBindings("java").getMember(mainClassName);) when attempting to get the main class.

Steps to reproduce the issue

  1. Set JAVA_HOME & GRAALVM_HOME so that they point to a graalvm installation with truffle/espresso and native image support.
  2. Create a runnable "client" jar containing the above trivial hello world program (let's call it "hello.jar").
  3. Create a runnable "host" jar containing the code above (let's call this one "espressoHost.jar").
  4. Run the host jar normally: LD_DEBUG=unused $GRAALVM_HOME/bin/java -jar espressoHost.jar hello.jar hello.Main and observe that espresso successfully runs the hello world program.
  5. Now create a native image of the host jar: native-image -jar espressoHost.jar --no-fallback --language:java, and execute it with the same arguments. this should result in the aforementioned error.

Expected behaviour
Expected the program to execute as it does before compilation as a native image.

Additional context
As part of a project I'm working on, I'm attempting to create a native image which can dynamically load and execute methods from an arbitrary jar file, specified at runtime. I'm unsure as to whether or not the error I'm getting is because of an internal graalvm bug, or a direct result of something I've done improperly.

I haven't been able to find anything regarding "espressoHome" on the graalvm docsite or in the graalvm demo code (Most of my experimentation with espresso has so far been accomplished by reverse engineering the espresso jshell demo). I'm currently reading the espresso source to try and better understand where this error comes from.

@ac101m ac101m added the truffle label May 9, 2022
@mukel mukel self-assigned this May 9, 2022
@mukel mukel added java on truffle Espresso Java17 and removed truffle labels May 9, 2022
@mukel
Copy link
Member

mukel commented May 10, 2022

This is rather a usability issue on our side, let me explain.

Currently, Espresso (bundled in a native image) is not fully standalone, the native executable only includes the interpreter/JIT+GC; but it also needs the .jars/modules and Java core native libraries like libjava.so, libnio.so ... to boot a guest JVM.
You can specify the guest Java home by passing --java.JavaHome=/path/to/java/home to the Espresso context, it even supports 8, 11 or 17 guest Java, regardless of the host Java version.

You can also customize the guest Java home as you please e.g. a jlinked version with only java.base. I've successfully ran Espresso with the (deprecated long time ago, but minimal) Java 8 compact1 profile.

But a "guest" Java home is not enough, Espresso itself requires a few extra libraries and .jars to run and provide additional functionality, briefly commented:

$ ls $GRAALVM_HOME/languages/java/lib
hotswap.jar     # Advanced redefinition capabilities API (optional)
libeden.so      # A library with workarounds for glibc dlmopen bugs. (optional)
libjavavm.so    # Espresso's core (optional)
libjvm.so       # JVM (native) API
libnespresso.so # JNI (native) API
polyglot.jar    # Guest Java API for interop (optional)

These files are located in the "Espresso Home" e.g. $GRAALVM_HOME/languages/java.

In JVM mode, Espresso (by default) reuses the same .jars/modules and native libraries bundled with GraalVM (which also includes the Espresso home).
In native mode, there's no GraalVM, just a native executable, we could build a special Java home including the .jars/modules and native libraries needed by the guest + Espresso home and put it in the executable ... but the easiest is to just pass the GraalVM home and let Espresso find all it needs there.
We haven't dig into a fully standalone embedding e.g. storing the guest Java home + Espresso home for the guest in the executable, since usually we have a GraalVM distribution around.

Do you need a full-blown GraalVM to run Espresso inside a native image?
No, but it's the easiest way. e.g. ./host -Dorg.graalvm.home="$GRAALVM_HOME"

I just opened a PR for the espresso-jshell demo to make this clear.

We could bundle all jars/modules within the native executable and unpack the native libraries to /tmp or have an extra folder with the native libraries since dlopen from memory is not possible.

BTW, awesome bug report!

@ac101m
Copy link
Author

ac101m commented May 10, 2022

Just tried this, and it seems to solve my immediate problem!

Currently, Espresso (bundled in a native image) is not fully standalone

We haven't dig into a fully standalone embedding e.g. storing the guest Java home + Espresso home for the guest in the executable

This is ultimately what we are trying to achieve. We're aware of some of the issues involved and have a few of ideas for working around them (some hackier than others). The first thing we'll probably try is, as you suggest here, writing temporary files somewhere and passing them to the context.

the easiest way. e.g. ./host -Dorg.graalvm.home="$GRAALVM_HOME"

A full graalvm installation probably isn't going to be viable in our case. You've given me plenty of information though so I should be able to work out how to slim things down a bit. That being said, is there another system property that can be used to manually set espresso home? Or is it always inferred from graalvm home? If so, the error message should probably reflect this!

I just opened a graalvm/graalvm-demos#115 to make this clear.

That's interesting. When I was experimenting with the jshell demo, I don't recall ever having to explicitly set org.graalvm.home to get it to run.

BTW, awesome bug report!

Thanks! Your comment has actually been super helpful and answered a few (semi) related questions I had about espresso. I have a much clearer understanding of what's going on now and should be able to make some progress. Thanks for the help!

@mukel
Copy link
Member

mukel commented May 11, 2022

When I was experimenting with the jshell demo, I don't recall ever having to explicitly set org.graalvm.home to get it to run.

That's my fault, the GraalVM home used at build time for espresso-jshell is persisted in the executable see here.

That being said, is there another system property that can be used to manually set espresso home? Or is it always inferred from graalvm home? If so, the error message should probably reflect this!

At some point there was an option to specify the Espresso home, I'm investigating how to bring it back, for now, replicating the folder structure does the job e.g. Espresso home must be located in $JAVA_HOME/languages/java.


As an experiment, I built a minimal guest+host Java home for espresso-jshell: a jlink-ed version of GraalVM 11 with only java.base + a (minimal) Espresso home, 26MB total, 9.2MB zipped. In this mode jshell can only use java.base, but that's a very usable subset of Java.

@rschatz
Copy link
Member

rschatz commented May 11, 2022

At some point there was an option to specify the Espresso home, I'm investigating how to bring it back

There is -Dorg.graalvm.language.<id>.home=.... See

if (name.startsWith("org.graalvm.language.") && name.endsWith(".home")) {

Also maybe useful: -Dorg.graalvm.launcher.relative.<id>.home=...

if (name.startsWith("org.graalvm.launcher.relative.") && name.endsWith(".home")) {

This one you can specify at native-image build time, that way you can hardcode a relative path in your main executable file. That way, you can provide a "package" containing your image and the needed language homes, and the whole thing is relocatable as long as the relative paths don't change.

@ac101m
Copy link
Author

ac101m commented May 16, 2022

Thank you both for your help! Your answers have been very informative.

I was eventually able to create a sample which worked in a regular linux environment, however because we aren't able to load dynamic libraries in the environment where we eventually need this to work, espresso may not be suitable.

I've been reading up a little on how the JNI works, and it looks as though from jdk8 onwards, the jdk contains a linker and can load static libraries at runtime. I have made some attempts to build the espresso native components statically, but I'm not very familiar with mx so I haven't been able to make much progress. Do you think this is possible?

@mukel
Copy link
Member

mukel commented May 17, 2022

native-image already statically links Java libraries, but these libraries cannot be used by multiple contexts e.g. host Java and guest Java are also considered different contexts.
There's a very rough prototype of a pure-Java version of Espresso with no native code dependencies e.g. all the "native" methods implemented in host Java. The caveat is that we cannot implement and/or maintain all the Java SE native methods, so it would be a strict subset of java.base. Would this mode fit your use case?

@linghengqian
Copy link

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

No branches or pull requests

4 participants