ninia / jep

Embed Python in Java
Other
1.3k stars 147 forks source link

Cannot use JEP from JVM, initialized from C++ native code #410

Open Daniel-Alievsky opened 2 years ago

Daniel-Alievsky commented 2 years ago

Hello! I successfully used JEP from usual Java environment, where JVM is called via "java.exe" (Windows-8).

But really I need to use it from little other environment: C++ server, which initializes JVM itself (JNI technology). And I see that my simplest test does not work.

This is my code:

    public static Object testJep() {
        final Interpreter context = new SharedInterpreter();
        context.exec("def test():\n    return 'Hello from JEP'\n");
        return context.invoke("test");
    }

This is the result:

java.lang.NullPointerException: Cannot invoke "java.lang.ClassLoader.getResourceAsStream(String)" because "<local6>" is null
    jep.ClassList.loadClassList(ClassList.java:282)
    jep.ClassList.<init>(ClassList.java:69)
    jep.ClassList.getInstance(ClassList.java:415)
    jep.Jep.setupJavaImportHook(Jep.java:202)
    jep.Jep.configureInterpreter(Jep.java:187)
    jep.SharedInterpreter.configureInterpreter(SharedInterpreter.java:64)
    jep.Jep.<init>(Jep.java:159)
    jep.SharedInterpreter.<init>(SharedInterpreter.java:56)
    com.siams.stare.extensions.tests.ExampleJep.testJep(ExampleJep.java:21)
    com.siams.stare.extensions.tests.ExampleJep.process(ExampleJep.java:17)
    net.algart.stare.execution.Executor.execute(Executor.java:429)

It seems to be your bug. Below is your code, that I found in debugger:

            while (in == null && i < classloadersToTry.length) {
                cl = classloadersToTry[i];
                in = cl.getResourceAsStream(rsc);  // line 282!!!
                i++;
            }

You think that classloadersToTry contains only non-null values, but it is not so:

        ClassLoader[] classloadersToTry = new ClassLoader[] {
                Thread.currentThread().getContextClassLoader(),
                Jep.class.getClassLoader() };

When JVM is started not by normal java.exe, but via JNI, Thread.currentThread().getContextClassLoader() will be null by default.

Could you fix this?

Daniel-Alievsky commented 2 years ago

I understand that there is workaround like the following:

class JepTools {
    static {
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        if (contextClassLoader == null) {
            Thread.currentThread().setContextClassLoader(JepTools.class.getClassLoader());
        }
    }
...
}

But you understand that this is a last resort and a highly undesirable solution for a generic library.

bsteffensmeier commented 2 years ago

This is a duplicate of #401 and a fix has already been written and will be included in the next release of Jep.

In the meantime if it is unnacceptable to set a context classloader for your thread you could also set a custom ClassEnquirerer in your JepConfig that uses a different ClassLoader.

Daniel-Alievsky commented 2 years ago

Ok, I prefer to wait for a new release. When, approximately, are you going to do this?

bsteffensmeier commented 2 years ago

I have added a Roadmap to the wiki with our release plans. Next release will come out around October 2022.