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

Status of JSR223 support #66

Open
fabrice-ducos opened this issue Jun 9, 2022 · 16 comments
Open

Status of JSR223 support #66

fabrice-ducos opened this issue Jun 9, 2022 · 16 comments
Assignees

Comments

@fabrice-ducos
Copy link

Hi everyone, thumbs up for the great work.

I am wondering if JPy is currently JSR223 compliant.

The ScriptEngine and ScriptEngineFactory implementations seem to be there. But no JPy engine is detected by the ScriptEngineManager when one tries to load it (I know it, because I can detect many other script engines without problem).

When checking at the jpy-0.10.0-SNAPSHOT.jar content, there is no META-INF/services/javax.script.ScriptEngineFactory, required for detection by the ScriptEngineManager.

Is it an oversight or on purpose?

@devinrsmith
Copy link
Member

I think @forman can speak to that. Looking at the javadoc I see this:

Note, jpy's JSR 223 support is not yet functional. This class is only used by unit-tests so far.

@devinrsmith devinrsmith self-assigned this Jun 10, 2022
@forman
Copy link
Member

forman commented Jun 13, 2022

Well, it's been long time ago... but I think @devinrsmith is right. The JSR 223 support hasn't been fully implemented yet. But I believe, this wouldn't be too much work.

@fabrice-ducos
Copy link
Author

fabrice-ducos commented Jul 14, 2022

Hi folks,

not sure if you have got any progress on this.

During a night of insomnia (caused by the current heat wave), I managed to launch a JSR223 compliant JPy interpreter (tested with the standard jrunscript JDK tool) with hopefully minimal changes in the code base. Basically:

  • I added the missing META-INF/services/javax.script.ScriptEngineFactory, required by the JSR223 specification
  • I updated jsr223/ScriptEngineImpl.java, in order to load the proper native library paths in the JVM at class loading time (in the static initialization block)
  • I updated jsr223/ScriptEngineFactoryImpl.java, adding "jpy" to the list of engine names, to avoid collision of engine names with other JSR223 Python implementations, e.g. jython (in the rare cases of applications launching several implementations of the same language), and to ensure that the ScriptManager will load JPy and not another Python/JVM implementation (if several are available in the classpath).

All in all, there must be less than 10 lines of difference with the official code base. You can review them at https://github.com/fabrice-ducos/jpy/tree/jsr223

In a separate branch https://github.com/fabrice-ducos/jpy/tree/jsr223-jpyinterp, I implemented a quick-and-dirty standalone JPy interpreter (jpyinterp.sh) based on jrunscript (a tool available in all JDK6+ installations, under $JAVA_HOME/bin). Since I do not master the way JPy configures its installation path, I had to look for Python and JPy native libraries in rather hackish ways. This is just for experimentation, so I preferred to keep it separate from the jsr223 branch. But it works on Linux and MacOSX (provided it is run from the jpy directory itself).

$ ./jpyinterp.sh 
cpython> print("Hello Jpy")
Hello Jpy
None
cpython>

Tested successfully on:

  • MacOS 12.4 Monterey with Oracle JDK8 1.8.0.181 and Python 3.10.1
  • Ubuntu 21.10 with OpenJDK17 and Python 3.9.7

@devinrsmith
Copy link
Member

Thanks for the work. I do have some concerns about the best way to initialize python. I see that the constructor of ScriptEngineImpl calls PyLib.startPython - I wonder if it makes more sense to move PyLibInitializer.initPyLib and PyLib.startPython right before ScriptEngineImpl is constructed in org.jpy.jsr223.ScriptEngineFactoryImpl#getScriptEngine?

@fabrice-ducos
Copy link
Author

Hi Devin,

thank you for your answer! I didn't touch ScriptEngineImpl and left the call to PyLib.startPython where it was. This was more an experiment of feasibility than a definitive proposal. PyLibInitializer.initPyLib didn't have to be called more than once (in my understanding), so an initialization block made sense to me. Now I am completely new to this project, and I do not have a global vision to give you a worthy opinion.

Feel free to rearrange the code as you see fit, for the good of the project.

@fabrice-ducos
Copy link
Author

After playing a bit more with the experimental jpyinterp.sh interpreter, it looks like there is a bit more work than expected.

While pure Python statements seem to work, the jpy module (for interaction between the JVM and Python) seems to have issues.

Here is a session borrowed from jpy 0.9.0 at https://jpy.readthedocs.io/en/latest/reference.html, and tested with the current version jpy 0.12.0

$ ./jpyinterp.sh 
cpython> import jpy
None
cpython> String = jpy.get_type('java.lang.String')
None
cpython> s = String('Hello jpy!')
java.lang.RuntimeException: Error in Python interpreter:
Type: <class 'TypeError'>
Value: 'java.lang.Class' object is not callable
Line: 1
Namespace: <module>
File: <string>
Traceback (most recent call last):
  File "<string>", line 1, in <module>

	at org.jpy.PyLib.executeCode(Native Method)
	at org.jpy.PyObject.executeCode(PyObject.java:138)
	at org.jpy.jsr223.ScriptEngineImpl.eval(ScriptEngineImpl.java:118)
	at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:264)
	at com.sun.tools.script.shell.Main.evaluateString(Main.java:298)
	at com.sun.tools.script.shell.Main.processSource(Main.java:267)
	at com.sun.tools.script.shell.Main.access$100(Main.java:37)
	at com.sun.tools.script.shell.Main$1.run(Main.java:183)
	at com.sun.tools.script.shell.Main.main(Main.java:48)

@devinrsmith
Copy link
Member

I've added a few commits on top of your branch, let me know what you think - see https://github.com/devinrsmith/jpy/tree/jsr223-jpyinterp. I think we could get the script engine changes merged soon if all looks good.

@fabrice-ducos
Copy link
Author

fabrice-ducos commented Jul 19, 2022

Looks good for me! Just not sure about the rationale behind the volatile modifier you added at ScriptEngineFactoryImpl.java:46

While I am sure that you added it for a very good reason, maybe a short comment would be welcome for explaining its necessity?

@devinrsmith
Copy link
Member

devinrsmith commented Jul 19, 2022

Mis-implemented double-checked locking patterns are pretty common - here's an article about it: https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html. Arguably, could be fixed as part of it's own issue / PR, but I've just gone ahead and fixed it here.

@fabrice-ducos
Copy link
Author

fabrice-ducos commented Jul 19, 2022

Thanks. Personally, I like to put a reference (under the form of a short comment, e.g. your reply in the former post, with the link) in the code itself (instead of a commit message) for this kind of clever implementation (not everyone is knowledgeable of these patterns). Of course, the link may become stale, but it is clear enough to be easy to find again (and to be updated) with a search engine.

@devinrsmith
Copy link
Member

devinrsmith commented Jul 20, 2022

I've added an additional commit with inline comments; as well as improved the getScriptEngine call.

@fabrice-ducos
Copy link
Author

fabrice-ducos commented Jul 20, 2022

Great! I think it's clear enough now.

While looking at the PyLib_CallAndReturnObject function from src/main/c/jni/org_jpy_PyLib.c,

I unearthed a commented piece of code:

  // Check why: for some reason, we don't need the following code to invoke object methods.
   /*
   if (isMethodCall) {
       PyObject* pyMethod;

       pyMethod = PyMethod_New(pyCallable, pyObject);
       if (pyMethod == NULL) {
           JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "PyLib_CallAndReturnObject: error: callable '%s': no memory\n", nameChars);
           PyLib_HandlePythonException(jenv);
           goto error;
       }
       JPy_DECREF(pyCallable);
       pyCallable = pyMethod;
   }
   */

Reenabling this snippet (as is, without change) does no good.
Unexpectedly, isMethodCall is true even on a statement like print("Hello")

Any chance that it be related with the issue on the jpy module that was illustrated in my post of the 15 june?

cpython> import jpy
None
cpython> String = jpy.get_type('java.lang.String')
None
cpython> s = String('Hello jpy!')
java.lang.RuntimeException: Error in Python interpreter:
Type: <class 'TypeError'>
Value: 'java.lang.Class' object is not callable

@devinrsmith
Copy link
Member

Can you link to the issue?

I think the implementation of ScriptEngineImpl may need to be changed to call into python "correctly". Also, I'm not sure how it's decided which method to call into wrt javax.script.ScriptEngine and javax.script.Invocable.

@fabrice-ducos
Copy link
Author

By linking to the issue, do you mean this ?

@devinrsmith
Copy link
Member

Oh sorry, I thought you meant a different issue. The object is not callable I think can be solved, but I think will take some debugging down into ScriptEngineImpl and maybe more knowledge on my part about JSR223.

When running interactively via python, it looks like this:

>>> import jpy
>>> String = jpy.get_type('java.lang.String')
>>> s = String('Hello jpy!')
>>> s
java.lang.String(objectRef=0x5568d3c6a7e0)

@fabrice-ducos
Copy link
Author

Maybe Using Java from Scripts can help.

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

No branches or pull requests

3 participants