diff --git a/src/main/java/org/openjdk/engine/python/PyObject.java b/src/main/java/org/openjdk/engine/python/PyObject.java index d9ff36b..0f893ed 100644 --- a/src/main/java/org/openjdk/engine/python/PyObject.java +++ b/src/main/java/org/openjdk/engine/python/PyObject.java @@ -510,9 +510,7 @@ public final boolean setAttribute(String name, Object obj) throws ScriptExceptio */ public final boolean deleteAttribute(PyObject name) throws ScriptException { checkEngine(name); - return pyEngine.withLock(() -> { - return PyObject_DelAttr(this.addr(), name.addr()) == 0; - }); + return pyEngine.withLock(() -> PyObject_DelAttr(this.addr(), name.addr()) == 0); } /** @@ -527,7 +525,11 @@ public final boolean deleteAttribute(String name) throws ScriptException { return pyEngine.withLock(() -> { var namePtr = pyEngine.toPyStringAddrNoLock(name); pyEngine.checkAndThrowPyExceptionNoLock(); - return PyObject_DelAttr(this.addr(), namePtr) == 0; + try { + return PyObject_DelAttr(this.addr(), namePtr) == 0; + } finally { + Py_DecRef(namePtr); + } }); } @@ -990,7 +992,7 @@ private PyObject[] toPyObjects(Object[] args) throws ScriptException { } else { if (args[i] instanceof PyObject pyObj) { checkEngine(pyObj); - if (!pyObj.engineManaged && !isConstant()) { + if (!pyObj.engineManaged && !pyObj.isConstant()) { Py_IncRef(pyObj.addr()); } pyArgs[i] = pyObj; diff --git a/src/main/java/org/openjdk/engine/python/PyObjectArena.java b/src/main/java/org/openjdk/engine/python/PyObjectArena.java index e04b534..001f87c 100644 --- a/src/main/java/org/openjdk/engine/python/PyObjectArena.java +++ b/src/main/java/org/openjdk/engine/python/PyObjectArena.java @@ -25,11 +25,11 @@ package org.openjdk.engine.python; +import javax.script.ScriptException; import java.util.Collections; import java.util.IdentityHashMap; import java.util.Objects; import java.util.Set; -import javax.script.ScriptException; import static org.openjdk.engine.python.bindings.Python_h.Py_DecRef; @@ -83,7 +83,7 @@ void unregister(PyObject pyObj) { * accidental reuse. Subsequent calls are no-ops. */ @Override - public void close() { + public synchronized void close() { if (pyObjSet != null) { try { pyEngine.withLock(() -> { diff --git a/src/main/java/org/openjdk/engine/python/PythonBindings.java b/src/main/java/org/openjdk/engine/python/PythonBindings.java index 8e70cd5..6493625 100644 --- a/src/main/java/org/openjdk/engine/python/PythonBindings.java +++ b/src/main/java/org/openjdk/engine/python/PythonBindings.java @@ -25,17 +25,9 @@ package org.openjdk.engine.python; -import java.lang.foreign.MemorySegment; -import java.util.AbstractMap; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - import javax.script.Bindings; import javax.script.ScriptException; +import java.util.*; /** * Bindings implementation backed by a Python dictionary (globals) for a specific @@ -116,7 +108,7 @@ public void putAll(Map toMerge) { * @param key non-null key (must be a String) * @return true if a mapping exists * @throws ClassCastException/IllegalArgumentException if key invalid - * @throws RuntimeException wrapping ScriptException on failure + * @throws RuntimeException wrapping ScriptException on failure */ @Override public boolean containsKey(Object key) { @@ -134,7 +126,7 @@ public boolean containsKey(Object key) { * @param key non-null key (must be a String) * @return the mapped value (as a PyObject) or null * @throws ClassCastException/IllegalArgumentException if key invalid - * @throws RuntimeException wrapping ScriptException on failure + * @throws RuntimeException wrapping ScriptException on failure */ @Override public Object get(Object key) { @@ -152,7 +144,7 @@ public Object get(Object key) { * @param key non-null key (must be a String) * @return the previous value associated with key, or null * @throws ClassCastException/IllegalArgumentException if key invalid - * @throws RuntimeException wrapping ScriptException on failure + * @throws RuntimeException wrapping ScriptException on failure */ @Override public Object remove(Object key) { @@ -170,11 +162,15 @@ public Object remove(Object key) { * Returns the number of key-value mappings in these bindings. * * @return the number of entries - * @throws RuntimeException wrapping ScriptException on failure + * @throws RuntimeException wrapping ScriptException on failure, or when the Python dictionary contains more than + * {@code Integer.MAX_VALUE} elements. */ @Override public int size() { try { + if (pyDict.size() > Integer.MAX_VALUE) { + throw new RuntimeException("Python dictionary is too large"); + } return (int) pyDict.size(); } catch (ScriptException ex) { throw new RuntimeException(ex); @@ -260,25 +256,27 @@ public Collection values() { */ @Override public Set> entrySet() { - Set> stringEntrySet = new HashSet<>(); + final var stringEntrySet = new HashSet>(); pyDict.getEngine().withPyObjectManager(() -> { try { PyList items = pyDict.items(); final int size = (int) items.size(); for (int i = 0; i < size; i++) { - PyTuple item = (PyTuple) items.getItem(i); - PyObject key = item.getItem(0); - PyObject value = item.getItem(1); - stringEntrySet.add(new AbstractMap.SimpleImmutableEntry<>(key.toString(), value.addr())); + final var item = (PyTuple) items.getItem(i); + final var key = item.getItem(0); + final var value = item.getItem(1); + final var entry = new AbstractMap.SimpleImmutableEntry<>(key.toString(), value.addr()); + // get the key and value while still under the object manager + stringEntrySet.add( + new AbstractMap.SimpleImmutableEntry<>( + entry.getKey(), + pyDict.pyEngine.wrap(entry.getValue()))); } } catch (ScriptException ex) { throw new RuntimeException(ex); } }); - return stringEntrySet. - stream(). - map(e -> new AbstractMap.SimpleImmutableEntry<>(e.getKey(), (Object)pyDict.pyEngine.wrap(e.getValue()))). - collect(Collectors.toSet()); + return stringEntrySet; } @Override @@ -299,7 +297,7 @@ public synchronized void close() { * Validates that a key is a non-empty String. * * @param key the key to validate (non-null) - * @throws ClassCastException if not a String + * @throws ClassCastException if not a String * @throws IllegalArgumentException if empty string */ private void checkKey(Object key) { diff --git a/src/main/java/org/openjdk/engine/python/PythonConfig.java b/src/main/java/org/openjdk/engine/python/PythonConfig.java index 745fc05..ceef925 100644 --- a/src/main/java/org/openjdk/engine/python/PythonConfig.java +++ b/src/main/java/org/openjdk/engine/python/PythonConfig.java @@ -24,6 +24,8 @@ */ package org.openjdk.engine.python; +import java.util.Objects; + /** * Manages various Python engine related System properties and other config items. */ @@ -61,7 +63,7 @@ public enum OS { * @return the detected OS value */ private static OS getCurrentOS() { - String osName = System.getProperty("os.name").toLowerCase(); + final var osName = Objects.requireNonNullElse(System.getProperty("os.name"), "unknown").toLowerCase(); if (osName.contains("linux")) { return OS.LINUX; } else if (osName.contains("mac")) { @@ -108,11 +110,12 @@ private static OS getCurrentOS() { */ public static final String SHARED_LIB_ABSPATH = System.getProperty("org.openjdk.engine.python.library.abspath"); + public static final String SYSTEM_PROPERTY_PREPEND_PATH_NAME = "org.openjdk.engine.python.sys.prepend.path"; /** * Path to prepend to the Python's module search path "sys.path". Default is * null. */ - public static final String SYS_PREPEND_PATH = System.getProperty("org.openjdk.engine.python.sys.prepend.path"); + public static final String SYS_PREPEND_PATH = System.getProperty(SYSTEM_PROPERTY_PREPEND_PATH_NAME); /** * Path to append to the Python's module search path "sys.path". Default is diff --git a/src/main/java/org/openjdk/engine/python/PythonMain.java b/src/main/java/org/openjdk/engine/python/PythonMain.java index 02706d0..2af8721 100644 --- a/src/main/java/org/openjdk/engine/python/PythonMain.java +++ b/src/main/java/org/openjdk/engine/python/PythonMain.java @@ -25,107 +25,109 @@ package org.openjdk.engine.python; +import org.openjdk.engine.python.AbstractPythonScriptEngine.PyExecMode; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; import java.io.File; import java.io.FileReader; -import java.io.InputStream; import java.io.IOException; +import java.io.InputStream; import java.util.NoSuchElementException; import java.util.Properties; import java.util.Scanner; -import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; -import javax.script.ScriptException; - -import org.openjdk.engine.python.AbstractPythonScriptEngine.PyExecMode; - // Simple REPL for the python script engine. final class PythonMain { - private PythonMain() {} + private PythonMain() { + } + public static void main(String[] args) { // make it consistent with Python REPL - if (System.getProperty("java.python.sys.prepend.path") == null) { - System.setProperty("java.python.sys.prepend.path", ""); + if (System.getProperty(PythonConfig.SYSTEM_PROPERTY_PREPEND_PATH_NAME) == null) { + System.setProperty(PythonConfig.SYSTEM_PROPERTY_PREPEND_PATH_NAME, ""); } ScriptEngineManager sem = new ScriptEngineManager(); - PythonScriptEngine e = (PythonScriptEngine) sem.getEngineByName("python"); - if (e == null) { - System.err.println("cannot find python script engine"); - System.exit(1); - } + try (final var e = (PythonScriptEngine) sem.getEngineByName("python")) { + if (e == null) { + System.err.println("cannot find python script engine"); + System.exit(1); + } - switch (args.length) { - case 1 -> { - if (args[0].equals("--version")) { - System.out.println("java.python script engine " + e.getFactory().getEngineVersion()); - e.setExecMode(PyExecMode.SINGLE); - try { - System.out.print("Python "); - e.eval("import sys; print(sys.version)"); - } catch (ScriptException ex) { - System.err.println(ex); - } - Properties props = new Properties(); - try (InputStream in = - PythonMain.class.getClassLoader().getResourceAsStream("git.properties")) { - if (in != null) { - props.load(in); - System.out.println("Commit ID: " + props.getProperty("git.commit.id")); - } - } catch (IOException ignored) {} - e.close(); - } else { - if (args[0].equals("-")) { + switch (args.length) { + case 1 -> { + if (args[0].equals("--version")) { + System.out.println("java.python script engine " + e.getFactory().getEngineVersion()); e.setExecMode(PyExecMode.SINGLE); - repl(e); - } else { try { - File f = new File(args[0]); - e.setExecMode(PyExecMode.FILE); - e.eval(new FileReader(f)); - } catch (ScriptException se) { - print(se); - } catch (IOException io) { - System.err.println(io); + System.out.print("Python "); + e.eval("import sys; print(sys.version)"); + } catch (ScriptException ex) { + System.err.println(ex); + } + Properties props = new Properties(); + try (InputStream in = + PythonMain.class.getClassLoader().getResourceAsStream("git.properties")) { + if (in != null) { + props.load(in); + System.out.println("Commit ID: " + props.getProperty("git.commit.id")); + } + } catch (IOException ignored) { + } + } else { + if (args[0].equals("-")) { + e.setExecMode(PyExecMode.SINGLE); + repl(e); + } else { + try { + File f = new File(args[0]); + e.setExecMode(PyExecMode.FILE); + e.eval(new FileReader(f)); + } catch (ScriptException se) { + print(se); + } catch (IOException io) { + System.err.println(io); + } } } } - } - case 2 -> { - switch (args[0]) { - case "-c" -> { - e.setExecMode(PyExecMode.FILE); - try { - e.eval(args[1]); - } catch (ScriptException se) { - print(se); + case 2 -> { + switch (args[0]) { + case "-c" -> { + e.setExecMode(PyExecMode.FILE); + try { + e.eval(args[1]); + } catch (ScriptException se) { + print(se); + } } - } - case "-m" -> { - e.setExecMode(PyExecMode.FILE); - try { - var str = String.format(""" - import runpy - import sys - sys.stdout.isatty = lambda: True - runpy.run_module('%s', run_name='__main__') - """, args[1]); - e.eval(str); - } catch (ScriptException se) { - print(se); + case "-m" -> { + e.setExecMode(PyExecMode.FILE); + try { + var str = String.format(""" + import runpy + import sys + sys.stdout.isatty = lambda: True + runpy.run_module('%s', run_name='__main__') + """, args[1]); + e.eval(str); + } catch (ScriptException se) { + print(se); + } } - } - default -> { - System.err.println("unknown option: " + args[0]); - System.exit(1); + default -> { + System.err.println("unknown option: " + args[0]); + System.exit(1); + } } } - } - default -> { - e.setExecMode(PyExecMode.SINGLE); - repl(e); + default -> { + e.setExecMode(PyExecMode.SINGLE); + repl(e); + } } } } @@ -139,7 +141,8 @@ private static void repl(ScriptEngine engine) { String line = null; try { line = in.nextLine(); - } catch (NoSuchElementException ignored) {} + } catch (NoSuchElementException ignored) { + } if (line == null || line.equalsIgnoreCase("exit")) { System.out.println("Bye!"); @@ -164,10 +167,10 @@ private static void repl(ScriptEngine engine) { } private static boolean isNone(Object obj) { - if (! (obj instanceof PyObject)) { + if (!(obj instanceof PyObject)) { return false; } - return ((PyObject)obj).isNone(); + return ((PyObject) obj).isNone(); } private static void print(ScriptException se) { diff --git a/src/main/java/org/openjdk/engine/python/PythonScriptEngine.java b/src/main/java/org/openjdk/engine/python/PythonScriptEngine.java index cd797f8..b8616eb 100644 --- a/src/main/java/org/openjdk/engine/python/PythonScriptEngine.java +++ b/src/main/java/org/openjdk/engine/python/PythonScriptEngine.java @@ -24,37 +24,25 @@ */ package org.openjdk.engine.python; +import org.openjdk.engine.python.bindings.*; + +import javax.script.Bindings; +import javax.script.ScriptContext; +import javax.script.ScriptEngineFactory; +import javax.script.ScriptException; import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; import java.io.StringWriter; import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; +import java.lang.ref.WeakReference; import java.lang.reflect.Proxy; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.function.Function; - -import javax.script.Bindings; -import javax.script.ScriptContext; -import javax.script.ScriptEngineFactory; -import javax.script.ScriptException; - -import static java.lang.foreign.MemorySegment.NULL; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collections; -import java.util.IdentityHashMap; import java.util.function.Supplier; -import org.openjdk.engine.python.bindings.PyConfig; -import org.openjdk.engine.python.bindings.PyCFunction; -import org.openjdk.engine.python.bindings.PyCFunctionFast; -import org.openjdk.engine.python.bindings.PyMemberDef; -import org.openjdk.engine.python.bindings.PyMethodDef; -import org.openjdk.engine.python.bindings.PyInterpreterConfig; -import org.openjdk.engine.python.bindings.PyThreadState; +import static java.lang.foreign.MemorySegment.NULL; import static org.openjdk.engine.python.bindings.Python_h.*; /** @@ -78,12 +66,9 @@ public final class PythonScriptEngine extends AbstractPythonScriptEngine { static { String mode = PythonConfig.GIL_MODE; GIL_MODE = switch (mode) { - case "default" -> - PyInterpreterConfig_DEFAULT_GIL(); - case "shared" -> - PyInterpreterConfig_SHARED_GIL(); - case "own" -> - PyInterpreterConfig_OWN_GIL(); + case "default" -> PyInterpreterConfig_DEFAULT_GIL(); + case "shared" -> PyInterpreterConfig_SHARED_GIL(); + case "own" -> PyInterpreterConfig_OWN_GIL(); default -> { System.err.println("unknown gil mode: " + mode + ", assuming default"); yield PyInterpreterConfig_DEFAULT_GIL(); @@ -135,7 +120,7 @@ static interface ThrowingFunction { private PythonScriptEngine(ScriptEngineFactory factory, /* PyTheadState* */ MemorySegment pyThreadState, - boolean mainInterpreter) { + boolean mainInterpreter) { this.factory = factory; this.mainInterpreter = mainInterpreter; this.engineArena = Arena.ofAuto(); @@ -189,13 +174,14 @@ public boolean isMainEngine() { } // ScriptEngine methods + /** * Evaluates the provided Python source using the supplied ScriptContext. * * @param script Python source code (non-null) - * @param sc ScriptContext providing globals and I/O + * @param sc ScriptContext providing globals and I/O * @return evaluation result wrapped as a PyObject - * @throws ScriptException if compilation or evaluation fails + * @throws ScriptException if compilation or evaluation fails * @throws IllegalStateException if the engine has been closed */ @Override @@ -220,9 +206,9 @@ public synchronized Object eval(String script, ScriptContext sc) throws ScriptEx * supplied ScriptContext. * * @param reader Reader providing Python source - * @param sc ScriptContext providing globals and I/O + * @param sc ScriptContext providing globals and I/O * @return evaluation result wrapped as a PyObject - * @throws ScriptException if reading, compilation, or evaluation fails + * @throws ScriptException if reading, compilation, or evaluation fails * @throws IllegalStateException if the engine has been closed */ @Override @@ -285,6 +271,7 @@ public ScriptEngineFactory getFactory() { } // Compilable methods + /** * Compiles the given Python source using the current ScriptContext. * @@ -324,6 +311,7 @@ public synchronized PythonCompiledScript compile(Reader reader) throws ScriptExc } // Invocable methods + /** * Invokes a method on a target Python object in this engine. * @@ -331,10 +319,10 @@ public synchronized PythonCompiledScript compile(Reader reader) throws ScriptExc * @param name method name (non-null) * @param args Java arguments to convert and pass * @return invocation result as a PyObject - * @throws ScriptException if conversion or invocation fails - * @throws NoSuchMethodException if the method cannot be resolved + * @throws ScriptException if conversion or invocation fails + * @throws NoSuchMethodException if the method cannot be resolved * @throws IllegalArgumentException if thiz is not a PyObject for this - * engine + * engine */ @Override public synchronized Object invokeMethod(Object thiz, String name, Object... args) @@ -360,7 +348,7 @@ public synchronized Object invokeMethod(Object thiz, String name, Object... args * @param name function name (non-null) * @param args Java arguments to convert and pass * @return invocation result as a PyObject - * @throws ScriptException if conversion or invocation fails + * @throws ScriptException if conversion or invocation fails * @throws NoSuchMethodException if no function by that name is found */ @Override @@ -383,7 +371,7 @@ public synchronized Object invokeFunction(String name, Object... args) * Returns a proxy implementing the given public interface by dispatching * calls to Python global functions of the same name. * - * @param interface type parameter + * @param interface type parameter * @param iface public interface to implement (non-null) * @return proxy instance * @throws IllegalArgumentException if iface is not a public interface @@ -399,12 +387,12 @@ public synchronized T getInterface(Class iface) { * Returns a proxy implementing the given public interface by dispatching * calls to methods on the specified Python object. * - * @param interface type parameter - * @param thiz PyObject to dispatch to (owned by this engine) + * @param interface type parameter + * @param thiz PyObject to dispatch to (owned by this engine) * @param iface public interface to implement (non-null) * @return proxy instance * @throws IllegalArgumentException if iface is not a public interface or - * thiz is invalid + * thiz is invalid */ @Override public synchronized T getInterface(Object thiz, Class iface) { @@ -422,6 +410,7 @@ public synchronized T getInterface(Object thiz, Class iface) { } // PyConverter methods + /** * Converts a Java String into a Python str belonging to this engine. * @@ -501,14 +490,14 @@ public synchronized PyObject fromJava(Object obj) throws ScriptException { * * @param func the Java function implementation * @param name the name of the Java function in Python code - * @param doc the doc String for the Java function + * @param doc the doc String for the Java function * @return a PyJavaFunction representing the converted Python function * object * @throws ScriptException if the type is unsupported or conversion fails */ @Override public PyJavaFunction fromJava(PyJavaFunction.Func func, - String name, String doc) throws ScriptException { + String name, String doc) throws ScriptException { return withLock(() -> new PyJavaFunction( PyJavaFunction.createPyCFunctionNoLock(this, func, name, doc), this)); } @@ -548,6 +537,7 @@ public synchronized Object toJava(Object obj, Class cls) throws ScriptExcepti } // Memory managed execution of the given Runnable + /** * Executes the given Runnable while tracking any PyObject instances created * during its execution. On exit, decref/cleanup is performed for the @@ -584,7 +574,7 @@ public final void withPyThreadState(Runnable r) { } withPyThreadStateInternal(pyState, r); synchronized (this) { - if (! isClosed()) { + if (!isClosed()) { pyVirtualThreadState.remove(); } } @@ -605,7 +595,7 @@ public final void withPyThreadState(Runnable r) { } withPyThreadStateInternal(pyState, r); synchronized (this) { - if (! isClosed()) { + if (!isClosed()) { pyPlatformThreadState.remove(); } } @@ -619,7 +609,7 @@ public final void withPyThreadState(Runnable r) { * * @param the type of the result of the operation * @param the type of the exception that may be thrown by the operation - * @param op ScopedValue.CallableOp to call within a managed PyObject arena + * @param op ScopedValue.CallableOp to call within a managed PyObject arena * @return the return value from the ScopedValue.CallableOp call * @throws X the exception thrown by ScopedValue.Callable is propagated */ @@ -792,12 +782,9 @@ synchronized T withLock(ThrowingSupplier supplier) throws ScriptException }); } catch (Exception ex) { switch (ex) { - case ScriptException sx -> - throw sx; - case RuntimeException rx -> - throw rx; - default -> - throw new RuntimeException(ex); + case ScriptException sx -> throw sx; + case RuntimeException rx -> throw rx; + default -> throw new RuntimeException(ex); } } } else { @@ -832,9 +819,16 @@ static MemorySegment getNotImplementedAddr() { void checkAndThrowPyExceptionNoLock() throws ScriptException { var pyExpAddr = PyErr_GetRaisedException(); if (!NULL.equals(pyExpAddr)) { - throw new PythonException( - wrap(pyExpAddr), - PyUnicode_AsUTF8(PyObject_Str(pyExpAddr)).getString(0)); + final var pyStr = PyObject_Str(pyExpAddr); + final String message; + if (NULL.equals(pyStr)) { + message = ""; + PyErr_Clear(); // clear the secondary error from PyObject_Str failing + } else { + message = PyUnicode_AsUTF8(pyStr).getString(0); // copies to Java heap + Py_DecRef(pyStr); // safe: Java String is already copied + } + throw new PythonException(wrap(pyExpAddr), message); } } @@ -858,12 +852,12 @@ synchronized void closeAllDependentEngines() { } // Internals only below this point - private void withPyThreadStateInternal(MemorySegment curThreadState, Runnable r) { + private void withPyThreadStateInternal(MemorySegment curThreadState, Runnable r) { try { r.run(); } finally { synchronized (this) { - if (! isClosed()) { + if (!isClosed()) { PyEval_AcquireThread(curThreadState); PyThreadState_Clear(curThreadState); PyThreadState_DeleteCurrent(); @@ -961,11 +955,10 @@ private MemorySegment createMethodDef( Objects.requireNonNull(func); Objects.requireNonNull(name); - Arena allocator = this.engineArena; - var pyMethodDef = PyMemberDef.allocate(allocator); - PyMemberDef.name(pyMethodDef, allocator.allocateFrom(name)); + var pyMethodDef = PyMethodDef.allocate(engineArena); + PyMethodDef.ml_name(pyMethodDef, engineArena.allocateFrom(name)); if (doc != null) { - PyMethodDef.ml_doc(pyMethodDef, allocator.allocateFrom(doc)); + PyMethodDef.ml_doc(pyMethodDef, engineArena.allocateFrom(doc)); } MemorySegment pyCFuncPtr; @@ -973,14 +966,14 @@ private MemorySegment createMethodDef( case PyJavaFunction.NoArgFunc noArgFunc -> { PyMethodDef.ml_flags(pyMethodDef, METH_STATIC() | METH_NOARGS()); pyCFuncPtr = PyCFunction.allocate((selfPtr, argPtr) - -> withPyThreadStateDetached( + -> withPyThreadStateDetached( () -> returnAddress(noArgFunc.call())), - allocator); + engineArena); } case PyJavaFunction.OneArgFunc oneArgFunc -> { PyMethodDef.ml_flags(pyMethodDef, METH_STATIC() | METH_O()); pyCFuncPtr = PyCFunction.allocate((selfPtr, argPtr) - -> withPyThreadStateDetached( + -> withPyThreadStateDetached( () -> { // do not track the argument by possible // PyObjectArena in context. This is borrowed refs @@ -988,12 +981,12 @@ private MemorySegment createMethodDef( final PyObject pyArg = wrap(argPtr).unregister(); return returnAddress(oneArgFunc.call(pyArg)); }), - allocator); + engineArena); } case PyJavaFunction.VarArgsFunc varArgsFunc -> { PyMethodDef.ml_flags(pyMethodDef, METH_STATIC() | METH_FASTCALL()); pyCFuncPtr = PyCFunctionFast.allocate((selfPtr, argsPtr, count) - -> withPyThreadStateDetached( + -> withPyThreadStateDetached( () -> { final PyObject[] pyArgs = new PyObject[(int) count]; for (int i = 0; i < pyArgs.length; i++) { @@ -1005,7 +998,7 @@ private MemorySegment createMethodDef( } return returnAddress(varArgsFunc.call(pyArgs)); }), - allocator); + engineArena); } } @@ -1142,7 +1135,7 @@ private T implementInterface(Class iface, PyDictionary pyDict) { } // create the engine backed by the main interpreter - private final class EngineLifeCycleManager { + private static final class EngineLifeCycleManager { private static PythonScriptEngine theEngine; @@ -1240,7 +1233,8 @@ private static PythonScriptEngine newSubEngineInternal(ScriptEngineFactory fac) // should not happen as we have checked error state?! throw new IllegalStateException("null new thread state!"); } - return new PythonScriptEngine(fac, PyEval_SaveThread(), false); + PyEval_SaveThread(); + return new PythonScriptEngine(fac, threadStatePtr, false); } } @@ -1257,7 +1251,7 @@ private static PythonScriptEngine newEngineInternal(PythonScriptEngineFactory fa } private static void closeInternal(PythonScriptEngine engine, - MemorySegment curThreadState) { + MemorySegment curThreadState) { PyEval_AcquireThread(curThreadState); synchronized (EngineLifeCycleManager.class) { if (theEngine == engine) { @@ -1296,10 +1290,8 @@ static void close(PythonScriptEngine engine) { }); } catch (Exception ex) { switch (ex) { - case RuntimeException rx -> - throw rx; - default -> - throw new RuntimeException(ex); + case RuntimeException rx -> throw rx; + default -> throw new RuntimeException(ex); } } } else { @@ -1319,10 +1311,8 @@ static PythonScriptEngine newEngine(PythonScriptEngineFactory fac) { }); } catch (Exception ex) { switch (ex) { - case RuntimeException rx -> - throw rx; - default -> - throw new RuntimeException(ex); + case RuntimeException rx -> throw rx; + default -> throw new RuntimeException(ex); } } } else { @@ -1386,18 +1376,18 @@ private static MemorySegment fromJavaNoLock(boolean b) { * Converts a supported Java object to a Python PyObject* for this engine. * Existing PyObject instances must belong to this engine, otherwise an * IllegalArgumentException is thrown. - * + *

* Ownership: for newly created objects, the caller owns a new reference. * For singletons (None/True/False), a borrowed singleton address is * returned. - * + *

* GIL: Must be called under withLock. * * @param obj Java value or PyObject * @return PyObject* address representing the value * @throws IllegalArgumentException if a PyObject belongs to a different - * engine - * @throws RuntimeException if the type is not supported + * engine + * @throws RuntimeException if the type is not supported */ private MemorySegment fromJavaNoLock(Object obj) { if (obj == null) { @@ -1478,7 +1468,7 @@ private PyObject compile(String script, ScriptContext sc) throws ScriptException * ScriptException by inspecting the Python exception state. * * @param compiled PyObject* address for the compiled code object - * @param sc ScriptContext providing globals/builtins + * @param sc ScriptContext providing globals/builtins * @return wrapped evaluation result * @throws ScriptException if evaluation fails */ diff --git a/src/test/java/org/openjdk/engine/python/test/SysPathEmptyPrependTest.java b/src/test/java/org/openjdk/engine/python/test/SysPathEmptyPrependTest.java index 0057ec0..855a71d 100644 --- a/src/test/java/org/openjdk/engine/python/test/SysPathEmptyPrependTest.java +++ b/src/test/java/org/openjdk/engine/python/test/SysPathEmptyPrependTest.java @@ -38,7 +38,7 @@ public class SysPathEmptyPrependTest extends AbstractSysPathTest { @BeforeClass public void createEngine() { // empty in sys.path means current directory - System.setProperty("org.openjdk.engine.python.sys.prepend.path", ""); + System.setProperty(PythonConfig.SYSTEM_PROPERTY_PREPEND_PATH_NAME, ""); ScriptEngineManager m = new ScriptEngineManager(); this.engine = (PythonScriptEngine) m.getEngineByName("python"); // use current directory as the module directory diff --git a/src/test/java/org/openjdk/engine/python/test/SysPathPrependTest.java b/src/test/java/org/openjdk/engine/python/test/SysPathPrependTest.java index d2b57b9..1cc2255 100644 --- a/src/test/java/org/openjdk/engine/python/test/SysPathPrependTest.java +++ b/src/test/java/org/openjdk/engine/python/test/SysPathPrependTest.java @@ -36,7 +36,7 @@ public class SysPathPrependTest extends AbstractSysPathTest { @BeforeClass public void init() throws IOException { - super.init("org.openjdk.engine.python.sys.prepend.path"); + super.init(PythonConfig.SYSTEM_PROPERTY_PREPEND_PATH_NAME); } @Override