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

Jpython #1176

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open

Jpython #1176

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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,5 @@ jacoco/
wheelhouse/
vc*.pdb
*.class
jpython
jpython.exe
5 changes: 5 additions & 0 deletions jpype/_bootstrap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from ._core import getDefaultJVMPath
import os.path

_JPypeJarPath = "file://" + os.path.join(os.path.dirname(os.path.dirname(__file__)), "org.jpype.jar")

Check notice

Code scanning / CodeQL

Unused global variable Note

The global variable '_JPypeJarPath' is not used.
_JPypeJVMPath = getDefaultJVMPath()

Check notice

Code scanning / CodeQL

Unused global variable Note

The global variable '_JPypeJVMPath' is not used.
2 changes: 1 addition & 1 deletion native/common/include/jp_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ class JPContext
bool isRunning();
void startJVM(const string& vmPath, const StringVector& args,
bool ignoreUnrecognized, bool convertStrings, bool interrupt);
void attachJVM(JNIEnv* env);
void attachJVM(JNIEnv* env, bool embedded);
void initializeResources(JNIEnv* env, bool interrupt);
void shutdownJVM(bool destroyJVM, bool freeJVM);
void attachCurrentThread();
Expand Down
5 changes: 3 additions & 2 deletions native/common/include/jp_proxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ class JPProxyType : public JPClass
const string& name,
JPClass* super,
JPClassList& interfaces,
jint modifiers);
jint modifiers,
jobjectArray args);
~ JPProxyType() override;

public: // JPClass implementation
Expand All @@ -98,4 +99,4 @@ class JPProxyType : public JPClass
jfieldID m_InstanceID;
} ;

#endif // JPPROXY_H
#endif // JPPROXY_H
17 changes: 15 additions & 2 deletions native/common/jp_classloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#include <pyjp.h>
#include <jp_classloader.h>

jobject urlClassLoader = NULL;

jobject JPClassLoader::getBootLoader()
{
return m_BootLoader.get();
Expand Down Expand Up @@ -111,12 +113,23 @@ JPClassLoader::JPClassLoader(JPJavaFrame& frame)

jclass JPClassLoader::findClass(JPJavaFrame& frame, const string& name)
{
#ifdef ANDROID
// We need to try the JVM classloader first in case org.jpype is already loaded
string cname = name;
for (int i = 0; i < cname.size(); ++i)
if (cname[i] == '.')
cname[i] = '/';
return frame.FindClass(cname);
jclass c = nullptr;
try {
c = frame.FindClass(cname);
if (c != nullptr)
return c;
}
catch (JPypeException& ex)
{
}

#ifdef ANDROID
return c;
#else
jvalue v[3];
v[0].l = frame.NewStringUTF(name.c_str());
Expand Down
10 changes: 5 additions & 5 deletions native/common/jp_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,10 @@ void JPContext::startJVM(const string& vmPath, const StringVector& args,
JP_TRACE_OUT;
}

void JPContext::attachJVM(JNIEnv* env)
void JPContext::attachJVM(JNIEnv* env, bool embedded)
{
env->GetJavaVM(&m_JavaVM);
#ifndef ANDROID
m_Embedded = true;
#endif
m_Embedded = embedded;
initializeResources(env, false);
}

Expand Down Expand Up @@ -216,6 +214,7 @@ void JPContext::initializeResources(JNIEnv* env, bool interrupt)

if (!m_Embedded)
{
printf("not embedded\n"); fflush(stdout);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is still a work in progress. So still a lot of rough edges. I have yet to come up with a logical reason that jpype needs to be started on main thread rather than on the spawned python thread. And i need to resolve how to prevent the user from calling shutdown.

That is unfortunately one of the key architectural problems with jpype. Shutdown should never have been exposed in the first place. The entry point should have spawned a thread, the original thread should have attached, then the launch thread should call destroy jvm. Modules in Python don't generally unload themselves or have time bombs to render themselves unusable, but that is what shutdown does.

Unfortunately like the calling classpath from startJVM as "-Dclasspath" which should never be done(it prevents our jar from being loaded requiring sideloading with a URLClassLoader), the shutdownJVM is in all the old docs and thus ilI will forever be forced to deal with.

JPPyObject import = JPPyObject::use(PyImport_AddModule("importlib.util"));
JPPyObject jpype = JPPyObject::call(PyObject_CallMethod(import.get(), "find_spec", "s", "_jpype"));
JPPyObject origin = JPPyObject::call(PyObject_GetAttrString(jpype.get(), "origin"));
Expand Down Expand Up @@ -310,7 +309,8 @@ void JPContext::shutdownJVM(bool destroyJVM, bool freeJVM)
JP_TRACE_IN("JPContext::shutdown");
if (m_JavaVM == nullptr)
JP_RAISE(PyExc_RuntimeError, "Attempt to shutdown without a live JVM");
// if (m_Embedded)
if (m_Embedded)
return;
// JP_RAISE(PyExc_RuntimeError, "Cannot shutdown from embedded Python");

// Wait for all non-demon threads to terminate
Expand Down
124 changes: 124 additions & 0 deletions native/common/jp_main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*****************************************************************************
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

See NOTICE file for details.
*****************************************************************************/
#include "jpype.h"
#include "pyjp.h"

void PyJPModule_installGC(PyObject* module);
void PyJPModule_loadResources(PyObject* module);

extern jobject urlClassLoader;

extern "C" JNIEXPORT void JNICALL Java_org_jpype_Main_initialize(
JNIEnv *env, jclass clazz,
jobject classLoader)
{
try
{
// Set the classloader so that we don't make a second one during bootstrapping
urlClassLoader = classLoader;
// Attach the JVM
JPContext_global->attachJVM(env, true);
} catch (JPypeException& ex)
{
printf("JPypeException\n");
} catch (...) // GCOVR_EXCL_LINE
{
printf("other exception\n");
}
}

extern "C" JNIEXPORT void JNICALL Java_org_jpype_Main_launch(
JNIEnv *env, jclass clazz,
jobjectArray args)
{
try {
int rc = 0;
// Fetch the Python modules
JPPyObject publicModule = JPPyObject::use(PyImport_ImportModule("jpype"));
JPPyObject privateModule = JPPyObject::use(PyImport_ImportModule("_jpype"));
JPPyObject builtins = JPPyObject::use(PyEval_GetBuiltins());
if (publicModule.isNull() || privateModule.isNull() || builtins.isNull())
{
fprintf(stderr, "Unable to find required resources\n");
return;
}

// Set up the GC
PyJPModule_installGC(privateModule.get());
PyObject_SetAttrString(builtins.get(), "jpype", publicModule.get());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The objective is to add jpype to the built-ins? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Like jython the goal was to have enough symbols available. Though i agree it is completely optional and i was just testing if ther was a pattern for adding to builtins.

PyJPModule_loadResources(privateModule.get());

// Copy the arguments
int argc = env->GetArrayLength(args);
char** argv = new char*[argc];
for (int i = 0; i<argc; ++i)
{
jboolean iscpy = 0;
jstring str = (jstring) env->GetObjectArrayElement(args, i);
const char* c = env->GetStringUTFChars(str, &iscpy);
argv[i] = strdup(c);
if (iscpy)
env->ReleaseStringUTFChars(str, c);
}

// Set isolated mode so that Python doesn't call exit
PyConfig config;
PyConfig_InitPythonConfig(&config);
config.isolated = 1;

PyStatus status = PyConfig_SetBytesArgv(&config, argc, argv);
if (PyStatus_Exception(status)) {
PyConfig_Clear(&config);
goto exception;
}

status = Py_InitializeFromConfig(&config);
if (PyStatus_Exception(status)) {
PyConfig_Clear(&config);
goto exception;
}
PyConfig_Clear(&config);

// Call Python from Java
rc = Py_RunMain();

// Problem: Python doesn't exist the main loop and return here.
// instead it finalizes and shutsdown everything even if there are
// other Java threads still using Python. This is apparently inherent
// in Python design. So it may not be possible to get them cleanly
// working together until Python splits there main loop like Java
// did

exception:

// Dump the memory
for (int i = 0; i<argc; ++i)
{
free(argv[i]);
}

// At this point there may be other threads launched by Java or Python so we
// can't cleanup. Just return control.
} catch (JPypeException& ex)
{
JP_TRACE("JPypeException raised");
printf("JPypeException\n");
} catch (...) // GCOVR_EXCL_LINE
{
printf("other exception\n");
}
return;
}
10 changes: 5 additions & 5 deletions native/common/jp_proxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,14 +236,14 @@ JPProxyType::JPProxyType(JPJavaFrame& frame,
const string& name,
JPClass* super,
JPClassList& interfaces,
jint modifiers)
jint modifiers,
jobjectArray args)
: JPClass(frame, clss, name, super, interfaces, modifiers)
{
jclass proxyClass = frame.FindClass("java/lang/reflect/Proxy");
jclass proxyClass = (jclass) frame.GetObjectArrayElement(args,0);
m_ProxyClass = JPClassRef(frame, proxyClass);
m_GetInvocationHandlerID = frame.GetStaticMethodID(proxyClass, "getInvocationHandler",
"(Ljava/lang/Object;)Ljava/lang/reflect/InvocationHandler;");
m_InstanceID = frame.GetFieldID(clss, "instance", "J");
m_GetInvocationHandlerID = frame.FromReflectedMethod(frame.GetObjectArrayElement(args,1));
m_InstanceID = frame.FromReflectedField(frame.GetObjectArrayElement(args,2));
}

JPProxyType::~JPProxyType()
Expand Down
5 changes: 3 additions & 2 deletions native/common/jp_typefactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ JNIEXPORT jlong JNICALL Java_org_jpype_manager_TypeFactoryNative_defineObjectCla
jstring name,
jlong superClass,
jlongArray interfacePtrs,
jint modifiers)
jint modifiers,
jobjectArray args)
{
// All resources are created here are owned by Java and deleted by Java shutdown routine
auto* context = (JPContext*) contextPtr;
Expand Down Expand Up @@ -278,7 +279,7 @@ JNIEXPORT jlong JNICALL Java_org_jpype_manager_TypeFactoryNative_defineObjectCla
if (className == "org.jpype.proxy.JPypeProxy")
return (jlong)
new JPProxyType(frame, cls, className,
(JPClass*) superClass, interfaces, modifiers);
(JPClass*) superClass, interfaces, modifiers, args);

// Register reflection types for later use
if (className == "java.lang.reflect.Method")
Expand Down
8 changes: 3 additions & 5 deletions native/java/org/jpype/JPypeContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.Buffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -98,12 +95,13 @@ static public JPypeContext getInstance()
* @param bootLoader is the classloader holding JPype resources.
* @return the created context.
*/
static JPypeContext createContext(long context, ClassLoader bootLoader, String nativeLib, boolean interrupt)
static JPypeContext createContext(long context, ClassLoader bootLoader, String nativeLib, boolean interrupt) throws Exception
{
if (nativeLib != null)
{
System.load(nativeLib);
}

INSTANCE.context = context;
INSTANCE.classLoader = (DynamicClassLoader) bootLoader;
INSTANCE.typeFactory = new TypeFactoryNative();
Expand All @@ -118,7 +116,7 @@ private JPypeContext()
{
}

void initialize(boolean interrupt)
void initialize(boolean interrupt) throws Exception
{
// Okay everything is setup so lets give it a go.
this.typeManager.init();
Expand Down
56 changes: 56 additions & 0 deletions native/java/org/jpype/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/** ***************************************************************************
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* See NOTICE file for details.
**************************************************************************** */
package org.jpype;

/**
*
* @author nelson85
*/
public class Main
{

/**
* Return control to Python to complete startup.
*
* @param args
*/
static native void launch(String[] args);

static native void initialize(ClassLoader loader);

/**
* Entry point for jpython.
*
* This code is only accessed from the JNI side.
*
* @param args
*/
static void mainX(String[] args, String nativeLib)
{
// Load the native library
System.load(nativeLib);

// Create a new main thread for Python
ClassLoader classLoader = Main.class.getClassLoader();
initialize(classLoader);

// Launch Python in a new thread
Thread thread = new Thread(() -> launch(args), "Python");
thread.start();
// launch(args);
// Return control to C so we can call Destroy and wait for shutdown.
}
}
12 changes: 12 additions & 0 deletions native/java/org/jpype/classloader/DynamicClassLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ public class DynamicClassLoader extends ClassLoader
public DynamicClassLoader(ClassLoader parent)
{
super(parent);
ClassLoader selfClassLoader = this.getClass().getClassLoader();
if (selfClassLoader instanceof URLClassLoader)
{
System.out.println("Side loaded");
loaders.add((URLClassLoader) selfClassLoader);
}
}

public int getCode()
Expand Down Expand Up @@ -146,6 +152,12 @@ public URL getResource(String name)
URL url = this.getParent().getResource(name);
if (url != null)
return url;

// Try the class loader that created this class
url = this.getClass().getClassLoader().getResource(name);
if (url != null)
return url;

for (ClassLoader cl : this.loaders)
{
url = cl.getResource(name);
Expand Down
Loading
Loading