Skip to content

Commit

Permalink
Rebase to master
Browse files Browse the repository at this point in the history
  • Loading branch information
jmao-denver committed Sep 23, 2024
1 parent 222d048 commit a8008de
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 2 deletions.
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
os.path.join(src_test_py_dir, 'jpy_java_embeddable_test.py'),
os.path.join(src_test_py_dir, 'jpy_obj_test.py'),
os.path.join(src_test_py_dir, 'jpy_eval_exec_test.py'),
os.path.join(src_test_py_dir, 'jpy_mt_eval_exec_test.py'),
]

# e.g. jdk_home_dir = '/home/marta/jdk1.7.0_15'
Expand Down Expand Up @@ -279,7 +280,7 @@ def test_python_with_java_classes(self):
def test_java(self):
assert test_maven()

suite.addTest(test_python_with_java_runtime)
# suite.addTest(test_python_with_java_runtime)
suite.addTest(test_python_with_java_classes)
# comment out because the asynchronous nature of the PyObject GC in Java makes stopPython/startPython flakey.
# suite.addTest(test_java)
Expand Down
4 changes: 4 additions & 0 deletions src/main/c/jpy_jmethod.c
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,8 @@ JPy_JMethod* JOverloadedMethod_FindMethod0(JNIEnv* jenv, JPy_JOverloadedMethod*
overloadedMethod->declaringClass->javaName, JPy_AS_UTF8(overloadedMethod->name), overloadCount, argCount);

for (i = 0; i < overloadCount; i++) {
// borrowed reference but no need to replace it with PyList_GetItemRef(), because the list won't be
// changed concurrently
currMethod = (JPy_JMethod*) PyList_GetItem(overloadedMethod->methodList, i);

if (currMethod->isVarArgs && matchValueMax > 0 && !bestMethod->isVarArgs) {
Expand Down Expand Up @@ -950,6 +952,8 @@ int JOverloadedMethod_AddMethod(JPy_JOverloadedMethod* overloadedMethod, JPy_JMe
// we need to insert this before the first varargs method
Py_ssize_t size = PyList_Size(overloadedMethod->methodList);
for (ii = 0; ii < size; ii++) {
// borrowed reference but no need to replace it with PyList_GetItemRef(), because the list won't be
// changed concurrently
PyObject *check = PyList_GetItem(overloadedMethod->methodList, ii);
if (((JPy_JMethod *) check)->isVarArgs) {
// this is the first varargs method, so we should go before it
Expand Down
2 changes: 1 addition & 1 deletion src/main/c/jpy_jobj.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ PyObject* JObj_FromType(JNIEnv* jenv, JPy_JType* type, jobject objectRef)
}


// we check the type translations dictionary for a callable for this java type name,
// we check the type translations dictionary for a callable for this java type name,
// and apply the returned callable to the wrapped object
callable = PyDict_GetItemString(JPy_Type_Translations, type->javaName);
if (callable != NULL) {
Expand Down
66 changes: 66 additions & 0 deletions src/main/c/jpy_jtype.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,54 @@
#include "jpy_conv.h"
#include "jpy_compat.h"

#ifdef Py_GIL_DISABLED
typedef struct {
PyMutex lock;
PyThreadState* owner;
int recursion_level;
} ReentrantLock;

static void acquire_lock(ReentrantLock* self) {
PyThreadState* current_thread = PyThreadState_Get();

if (self->owner == current_thread) {
self->recursion_level++;
return;
}

PyMutex_Lock(&(self->lock));

self->owner = current_thread;
self->recursion_level = 1;
}

static void release_lock(ReentrantLock* self) {
if (self->owner != PyThreadState_Get()) {
PyErr_SetString(PyExc_RuntimeError, "Lock not owned by current thread");
return;
}

self->recursion_level--;
if (self->recursion_level == 0) {
self->owner = NULL;
PyMutex_Unlock(&(self->lock));
}
}

static ReentrantLock get_type_rlock = {{0}, NULL, 0};
static ReentrantLock resolve_type_rlock = {{0}, NULL, 0};

#define ACQUIRE_GET_TYPE_LOCK() acquire_lock(&get_type_rlock)
#define RELEASE_GET_TYPE_LOCK() release_lock(&get_type_rlock)
#define ACQUIRE_RESOLVE_TYPE_LOCK() acquire_lock(&resolve_type_rlock)
#define RELEASE_RESOLVE_TYPE_LOCK() release_lock(&resolve_type_rlock)

#else
#define ACQUIRE_GET_TYPE_LOCK()
#define RELEASE_GET_TYPE_LOCK()
#define ACQUIRE_RESOLVE_TYPE_LOCK()
#define RELEASE_RESOLVE_TYPE_LOCK()
#endif

JPy_JType* JType_New(JNIEnv* jenv, jclass classRef, jboolean resolve);
int JType_ResolveType(JNIEnv* jenv, JPy_JType* type);
Expand All @@ -52,6 +100,8 @@ static int JType_MatchVarArgPyArgAsFPType(const JPy_ParamDescriptor *paramDescri
static int JType_MatchVarArgPyArgIntType(const JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx,
struct JPy_JType *expectedComponentType);



JPy_JType* JType_GetTypeForObject(JNIEnv* jenv, jobject objectRef, jboolean resolve)
{
JPy_JType* type;
Expand Down Expand Up @@ -151,6 +201,7 @@ JPy_JType* JType_GetType(JNIEnv* jenv, jclass classRef, jboolean resolve)
return NULL;
}

ACQUIRE_GET_TYPE_LOCK();
typeValue = PyDict_GetItem(JPy_Types, typeKey);
if (typeValue == NULL) {

Expand All @@ -160,6 +211,7 @@ JPy_JType* JType_GetType(JNIEnv* jenv, jclass classRef, jboolean resolve)
type = JType_New(jenv, classRef, resolve);
if (type == NULL) {
JPy_DECREF(typeKey);
RELEASE_GET_TYPE_LOCK();
return NULL;
}

Expand All @@ -184,6 +236,7 @@ JPy_JType* JType_GetType(JNIEnv* jenv, jclass classRef, jboolean resolve)
PyDict_DelItem(JPy_Types, typeKey);
JPy_DECREF(typeKey);
JPy_DECREF(type);
RELEASE_GET_TYPE_LOCK();
return NULL;
}

Expand All @@ -195,6 +248,7 @@ JPy_JType* JType_GetType(JNIEnv* jenv, jclass classRef, jboolean resolve)
PyDict_DelItem(JPy_Types, typeKey);
JPy_DECREF(typeKey);
JPy_DECREF(type);
RELEASE_GET_TYPE_LOCK();
return NULL;
}

Expand All @@ -206,6 +260,7 @@ JPy_JType* JType_GetType(JNIEnv* jenv, jclass classRef, jboolean resolve)
PyDict_DelItem(JPy_Types, typeKey);
JPy_DECREF(typeKey);
JPy_DECREF(type);
RELEASE_GET_TYPE_LOCK();
return NULL;
}

Expand All @@ -231,6 +286,7 @@ JPy_JType* JType_GetType(JNIEnv* jenv, jclass classRef, jboolean resolve)
"jpy internal error: attributes in 'jpy.%s' must be of type '%s', but found a '%s'",
JPy_MODULE_ATTR_NAME_TYPES, JType_Type.tp_name, Py_TYPE(typeValue)->tp_name);
JPy_DECREF(typeKey);
RELEASE_GET_TYPE_LOCK();
return NULL;
}

Expand All @@ -240,6 +296,7 @@ JPy_JType* JType_GetType(JNIEnv* jenv, jclass classRef, jboolean resolve)
}

JPy_DIAG_PRINT(JPy_DIAG_F_TYPE, "JType_GetType: javaName=\"%s\", found=%d, resolve=%d, resolved=%d, type=%p\n", type->javaName, found, resolve, type->isResolved, type);
RELEASE_GET_TYPE_LOCK();

if (!type->isResolved && resolve) {
if (JType_ResolveType(jenv, type) < 0) {
Expand Down Expand Up @@ -968,7 +1025,10 @@ int JType_ResolveType(JNIEnv* jenv, JPy_JType* type)
{
PyTypeObject* typeObj;

ACQUIRE_RESOLVE_TYPE_LOCK();

if (type->isResolved || type->isResolving) {
RELEASE_RESOLVE_TYPE_LOCK();
return 0;
}

Expand All @@ -980,6 +1040,7 @@ int JType_ResolveType(JNIEnv* jenv, JPy_JType* type)
if (!baseType->isResolved) {
if (JType_ResolveType(jenv, baseType) < 0) {
type->isResolving = JNI_FALSE;
RELEASE_RESOLVE_TYPE_LOCK();
return -1;
}
}
Expand All @@ -988,24 +1049,29 @@ int JType_ResolveType(JNIEnv* jenv, JPy_JType* type)
//printf("JType_ResolveType 1\n");
if (JType_ProcessClassConstructors(jenv, type) < 0) {
type->isResolving = JNI_FALSE;
RELEASE_RESOLVE_TYPE_LOCK();
return -1;
}

//printf("JType_ResolveType 2\n");
if (JType_ProcessClassMethods(jenv, type) < 0) {
type->isResolving = JNI_FALSE;
RELEASE_RESOLVE_TYPE_LOCK();
return -1;
}

//printf("JType_ResolveType 3\n");
if (JType_ProcessClassFields(jenv, type) < 0) {
type->isResolving = JNI_FALSE;
RELEASE_RESOLVE_TYPE_LOCK();
return -1;
}

//printf("JType_ResolveType 4\n");
type->isResolving = JNI_FALSE;
type->isResolved = JNI_TRUE;

RELEASE_RESOLVE_TYPE_LOCK();
return 0;
}

Expand Down
3 changes: 3 additions & 0 deletions src/main/c/jpy_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,9 @@ PyMODINIT_FUNC JPY_MODULE_INIT_FUNC(void)
if (JPy_Module == NULL) {
JPY_RETURN(NULL);
}
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(JPy_Module, Py_MOD_GIL_NOT_USED);
#endif
#elif defined(JPY_COMPAT_27)
JPy_Module = Py_InitModule3(JPY_MODULE_NAME, JPy_Functions, JPY_MODULE_DOC);
if (JPy_Module == NULL) {
Expand Down
54 changes: 54 additions & 0 deletions src/test/java/org/jpy/fixtures/MultiThreadedEvalTestFixture.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.jpy.fixtures;

import org.jpy.PyInputMode;
import org.jpy.PyLib;
import org.jpy.PyObject;

import java.util.List;

public class MultiThreadedEvalTestFixture {

public static void expression(String expression, int numThreads) {
PyObject globals = PyLib.getCurrentGlobals();
PyObject locals = PyLib.getCurrentLocals();

List<Thread> threads = new java.util.ArrayList<>();
for (int i = 0; i < numThreads; i++) {
threads.add(new Thread(() -> {
PyObject.executeCode(expression, PyInputMode.EXPRESSION, globals, locals);
}));
}
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}

public static void script(String expression, int numThreads) {
List<Thread> threads = new java.util.ArrayList<>();
PyObject globals = PyLib.getCurrentGlobals();
PyObject locals = PyLib.getCurrentLocals();
for (int i = 0; i < numThreads; i++) {
threads.add(new Thread(() -> {
PyObject.executeCode(expression, PyInputMode.SCRIPT, globals, locals);
}));
}
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}

}
1 change: 1 addition & 0 deletions src/test/python/jpy_eval_exec_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
jpyutil.init_jvm(jvm_maxmem='512M', jvm_classpath=['target/classes', 'target/test-classes'])
import jpy


class TestEvalExec(unittest.TestCase):
def setUp(self):
self.fixture = jpy.get_type("org.jpy.fixtures.EvalTestFixture")
Expand Down
Loading

0 comments on commit a8008de

Please sign in to comment.