Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions samples/RemoteEngine.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@

/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

import javax.script.*;

import org.openjdk.engine.python.AbstractPythonScriptEngine.PyExecMode;
import org.openjdk.engine.python.PythonException;
import org.openjdk.engine.python.PythonRemoteCompiledScript;
import org.openjdk.engine.python.PythonRemoteScriptEngine;
import org.openjdk.engine.python.PythonScriptEngine;


void main() throws IOException {
System.setProperty("org.openjdk.engine.python.sys.prepend.path", "");
var m = new ScriptEngineManager();
var e = (PythonScriptEngine) m.getEngineByName("python");
e.setExecMode(PyExecMode.FILE);

try {
e.eval("import os");
e.setExecMode(PyExecMode.EVAL);
IO.println("local pid = " + e.eval("os.getpid()"));

e.setExecMode(PyExecMode.FILE);
e.eval("from squaring import *");
IO.println(e.invokeFunction("square", 27));
e.setExecMode(PyExecMode.EVAL);
var numbers = e.eval("[23, 44, 12]");

var re = PythonRemoteScriptEngine.create(e);
re.setExecMode(PyExecMode.FILE);
re.eval("import os");
re.setExecMode(PyExecMode.EVAL);
IO.println("remote pid = " + re.eval("os.getpid()"));
re.setExecMode(PyExecMode.FILE);
re.eval("from squaring import *");
// we can call global functions on the remote engine
IO.println(re.invokeFunction("square", 27));
// we can call global functions on the remote engine by passing
// arbitaray Python picklable arguments from the local engine
IO.println("sum of " + numbers + " is " + re.invokeFunction("sum", numbers));

// we can compile script remotely and eval it many times
var reCompiled = (PythonRemoteCompiledScript) re.compile("""
for i in range(10):
print(i*i)
""");

IO.println(reCompiled);
reCompiled.eval();

// closed the compiled script
reCompiled.close();

IO.println(reCompiled);
// cannot eval after close!
reCompiled.eval();
} catch (ScriptException se) {
if (se instanceof PythonException pe) {
pe.print();
} else {
se.printStackTrace();
}
} catch (NoSuchMethodException nsme) {
nsme.printStackTrace();
}
}
75 changes: 75 additions & 0 deletions samples/RemoteObject.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@

/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

import javax.script.*;

import org.openjdk.engine.python.AbstractPythonScriptEngine.PyExecMode;
import org.openjdk.engine.python.PythonRemoteScriptEngine;
import org.openjdk.engine.python.PythonScriptEngine;

// By default remote engine sends pickled objects. If you call
// any method on such objects, methods are executed locally.
// But, sometimes we may to access method(s) of a remote object
// so that methods will run on the remote process.
void main() throws Exception {
var m = new ScriptEngineManager();
var e = (PythonScriptEngine) m.getEngineByName("python");
e.setExecMode(PyExecMode.FILE);

var re = PythonRemoteScriptEngine.create(e);
re.setExecMode(PyExecMode.FILE);
re.eval("import os");
re.eval("""
class MyClass:
def __init__(self, x):
self.x = x
def add(self, y):
print("pid =", os.getpid())
print(self.x + y)
def call(self):
print("pid =", os.getpid())
return self.x
""");

re.setExecMode(PyExecMode.EVAL);

// script has to register an object as remote object!
var remoteObj = re.eval("RemoteObjectManager.register(MyClass(25))");
IO.println(remoteObj);

// call "func" method on the remote object
re.invokeMethod(remoteObj, "add", 233);

// get Java interface object backed by methods of remote object
var callable = re.getInterface(remoteObj, Callable.class);
IO.println(callable.call());
}
34 changes: 34 additions & 0 deletions samples/squaring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# - Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# - Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# - Neither the name of Oracle nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import os

def square(x):
print(f"squaring {x} from process {os.getpid()}")
return x*x
19 changes: 18 additions & 1 deletion src/main/java/org/openjdk/engine/python/PythonConfig.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -126,11 +126,28 @@ private static OS getCurrentOS() {
*/
public static final boolean JAVASTACK_IN_PYEXCEPTION;

/**
* Perform Python GC before engine close. Default is true.
*/
public static final boolean PYTHON_GC_ON_CLOSE;

/**
* Perform Java GC after engine close. Default is true.
*/
public static final boolean JAVA_GC_ON_CLOSE;

static {
String propVal = System.getProperty("org.openjdk.engine.python.javastack_in_pyexception", "true");
JAVASTACK_IN_PYEXCEPTION = Boolean.parseBoolean(propVal);

propVal = System.getProperty("org.openjdk.engine.python.gc.on.close", "true");
PYTHON_GC_ON_CLOSE = Boolean.parseBoolean(propVal);

propVal = System.getProperty("org.openjdk.engine.python.java.gc.on.close", "true");
JAVA_GC_ON_CLOSE = Boolean.parseBoolean(propVal);
}


/**
* Returns the configured Python program name if explicitly set via the
* "java.python.program.name" system property, otherwise null.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

package org.openjdk.engine.python;

import java.util.Objects;
import javax.script.CompiledScript;
import javax.script.ScriptContext;
import javax.script.ScriptException;

/**
* A compiled Python code object evaluated by a remote Python interpreter and
* coordinated via a local {@link PythonRemoteScriptEngine}.
* <p>
* The underlying code object lives in the remote interpreter; this wrapper
* allows evaluation and lifecycle control from Java. Calling {@link #close()}
* releases the remote resource and makes this instance unusable.
*/
public final class PythonRemoteCompiledScript extends CompiledScript implements AutoCloseable {

private final PythonRemoteScriptEngine pyEngine;
private PyObject pyScript;

/**
* Constructs a remote compiled script wrapper.
*
* @param pyEngine the remote script engine coordinating evaluation (non-null)
* @param scriptObj the remote CPython code object as a PyObject (non-null)
* @throws NullPointerException if either argument is null
*/
PythonRemoteCompiledScript(PythonRemoteScriptEngine pyEngine, PyObject scriptObj) {
this.pyEngine = Objects.requireNonNull(pyEngine);
this.pyScript = Objects.requireNonNull(scriptObj);
}

/**
* Evaluates this compiled script using the provided script context in the
* remote interpreter.
*
* @param ctxt the script context providing bindings and I/O
* @return the result of evaluating the code object
* @throws ScriptException if remote evaluation fails
* @throws IllegalStateException if this compiled script has been closed
*/
@Override
public Object eval(ScriptContext ctxt) throws ScriptException {
checkClosed();
return pyEngine.evalCompiled(pyScript, ctxt);
}

/**
* Evaluates this compiled script using the engine's current context in the
* remote interpreter.
*
* @return the result of evaluating the code object
* @throws ScriptException if remote evaluation fails
* @throws IllegalStateException if this compiled script has been closed
*/
@Override
public synchronized Object eval() throws ScriptException {
checkClosed();
return pyEngine.evalCompiled(pyScript);
}

/**
* Returns the remote engine that produced this compiled script.
*
* @return the owning PythonRemoteScriptEngine
*/
@Override
public PythonRemoteScriptEngine getEngine() {
return pyEngine;
}

/**
* Releases the remote code object and marks this instance as closed. Further
* evaluation attempts will throw IllegalStateException. This method is idempotent.
*/
@Override
public synchronized void close() {
if (pyScript != null) {
try {
pyEngine.closeCompiled(pyScript);
} catch (ScriptException ex) {
throw new RuntimeException(ex);
} finally {
pyScript.destroy();
pyScript = null;
}
}
}

/**
* Returns a debug-friendly string describing this compiled script.
*
* @return a string representation including the underlying remote code object
*/
@Override
public String toString() {
return String.format("PythonRemoteCompiledScript(%s)", pyScript);
}

/**
* Ensures this compiled script has not been closed.
*
* @throws IllegalStateException if the script has been closed
*/
private void checkClosed() {
if (pyScript == null) {
throw new IllegalStateException("CompiledScript closed already");
}
}
}
Loading