소스 검색

HPCC-8030 Java/Python/Javascript language support in ECL

Fix some threading/signal issues in the java embed plugin.

Signed-off-by: Richard Chapman <rchapman@hpccsystems.com>
Richard Chapman 12 년 전
부모
커밋
a71e769723
2개의 변경된 파일40개의 추가작업 그리고 9개의 파일을 삭제
  1. 12 0
      plugins/javaembed/CMakeLists.txt
  2. 28 9
      plugins/javaembed/javaembed.cpp

+ 12 - 0
plugins/javaembed/CMakeLists.txt

@@ -46,7 +46,19 @@ ADD_DEFINITIONS( -D_USRDLL -DJAVAEMBED_EXPORTS )
 HPCC_ADD_LIBRARY( javaembed SHARED ${SRCS} )
 install ( TARGETS javaembed DESTINATION plugins )
 
+# We link against jsig so that signals are chained from the jvm
+
+get_filename_component(JAVA_LIBRARY_PATH ${JAVA_AWT_LIBRARY} PATH)
+if (APPLE)
+    set(JSIG_LIBRARY ${JAVA_LIBRARY_PATH}/libjsig.dylib)
+elseif (WIN32)
+    set(JSIG_LIBRARY ${JAVA_LIBRARY_PATH}/jsig.dll)
+else()
+    set(JSIG_LIBRARY ${JAVA_LIBRARY_PATH}/libjsig.so)
+endif()
+
 target_link_libraries ( javaembed
+    ${JSIG_LIBRARY}
     ${JNI_LIBRARIES}
     eclrtl
     jlib

+ 28 - 9
plugins/javaembed/javaembed.cpp

@@ -20,6 +20,7 @@
 #include "eclrtl.hpp"
 #include "jexcept.hpp"
 #include "jthread.hpp"
+#include "jmutex.hpp"
 #include "hqlplugins.hpp"
 
 #ifdef _WIN32
@@ -62,7 +63,12 @@ extern "C" EXPORT bool getECLPluginDefinition(ECLPluginDefinitionBlock *pb)
 
 namespace javaembed {
 
-// Use a global object to ensure that the Java VM  is initialized on main thread
+// Use a global object to ensure that the Java VM  is initialized once only.
+// We create it lazily for two reasons:
+// 1. So that we only get a JVM if we need onr (even if we have loaded the plugin)
+// 2. It's important for the JVM to be initialized AFTER we have set up signal handlers, as it
+//    likes to set its own (in particular, it seems to intercept and ignore some SIGSEGV during the
+//    garbage collection).
 
 static class JavaGlobalState
 {
@@ -72,11 +78,11 @@ public:
         JavaVMInitArgs vm_args; /* JDK/JRE 6 VM initialization arguments */
         JavaVMOption* options = new JavaVMOption[3];
         options[0].optionString = (char *) "-Djava.class.path=.";
-        options[1].optionString = (char *) "-verbosegc";
+        options[1].optionString = (char *) "-Xcheck:jni";
         options[2].optionString = (char *) "-verbose:jni";
         vm_args.version = JNI_VERSION_1_6;
 #ifdef _DEBUG
-        vm_args.nOptions = 1;  // set to 3 if you want the verbose...
+        vm_args.nOptions = 2;  // set to 3 if you want the verbose...
 #else
         vm_args.nOptions = 1;
 #endif
@@ -93,7 +99,16 @@ public:
         // We don't attempt to destroy the Java VM, as it's buggy...
     }
     JavaVM *javaVM;       /* denotes a Java VM */
-} globalState;
+} *globalState = NULL;
+
+static CriticalSection globalStateCrit;
+static JavaGlobalState *queryGlobalState()
+{
+    CriticalBlock b(globalStateCrit);
+    if (!globalState)
+        globalState = new JavaGlobalState;
+    return globalState;
+}
 
 // There is a singleton JavaThreadContext per thread. This allows us to
 // ensure that we can make repeated calls to a Java function efficiently.
@@ -105,13 +120,15 @@ public:
 public:
     JavaThreadContext()
     {
-        jint res = globalState.javaVM->AttachCurrentThread((void **) &JNIenv, NULL);
+        jint res = queryGlobalState()->javaVM->AttachCurrentThread((void **) &JNIenv, NULL);
         assertex(res >= 0);
         javaClass = NULL;
         javaMethodID = NULL;
     }
     ~JavaThreadContext()
     {
+        if (javaClass)
+            JNIenv->DeleteGlobalRef(javaClass);
     }
 
     inline void importFunction(const char *text)
@@ -128,6 +145,8 @@ public:
             funcname++;  // skip the '.'
             StringBuffer methodname(signature-funcname, funcname);
             signature++; // skip the ':'
+            if (javaClass)
+                JNIenv->DeleteGlobalRef(javaClass);
             javaClass = (jclass) JNIenv->NewGlobalRef(JNIenv->FindClass(classname));
             if (!javaClass)
                 throw MakeStringException(MSGAUD_user, 0, "javaembed: Failed to resolve class name %s", classname.str());
@@ -150,7 +169,7 @@ public:
         case 'J': result.j = JNIenv->CallStaticLongMethodA(javaClass, javaMethodID, args); break;
         case 'F': result.f = JNIenv->CallStaticFloatMethodA(javaClass, javaMethodID, args); break;
         case 'D': result.d = JNIenv->CallStaticDoubleMethodA(javaClass, javaMethodID, args); break;
-        case 'L': result.l = JNIenv->NewGlobalRef(JNIenv->CallStaticObjectMethodA(javaClass, javaMethodID, args)); break;
+        case 'L': result.l = JNIenv->CallStaticObjectMethodA(javaClass, javaMethodID, args); break;
         case 'I': // Others are all smaller ints, so we can use this for all
         default: result.i = JNIenv->CallStaticIntMethodA(javaClass, javaMethodID, args); break;
         }
@@ -166,7 +185,7 @@ public:
         case 'L':
             {
                 // Result should be of class 'Number'
-                jmethodID getVal = JNIenv->GetMethodID(JNIenv->GetObjectClass(result.l), "longValue", "()J");  // this could probably be cached?
+                jmethodID getVal = JNIenv->GetMethodID(JNIenv->GetObjectClass(result.l), "longValue", "()J");
                 if (!getVal)
                     throw MakeStringException(MSGAUD_user, 0, "javaembed: Type mismatch on result");
                 return JNIenv->CallLongMethod(result.l, getVal);
@@ -184,7 +203,7 @@ public:
         case 'L':
             {
                 // Result should be of class 'Number'
-                jmethodID getVal = JNIenv->GetMethodID(JNIenv->GetObjectClass(result.l), "doubleValue", "()D");  // this could probably be cached?
+                jmethodID getVal = JNIenv->GetMethodID(JNIenv->GetObjectClass(result.l), "doubleValue", "()D");
                 if (!getVal)
                     throw MakeStringException(MSGAUD_user, 0, "javaembed: Type mismatch on result");
                 return JNIenv->CallDoubleMethod(result.l, getVal);
@@ -201,7 +220,7 @@ public:
         case 'L':
             {
                 // Result should be of class 'Boolean'
-                jmethodID getVal = JNIenv->GetMethodID(JNIenv->GetObjectClass(result.l), "booleanValue", "()Z");  // this could probably be cached?
+                jmethodID getVal = JNIenv->GetMethodID(JNIenv->GetObjectClass(result.l), "booleanValue", "()Z");
                 if (!getVal)
                     throw MakeStringException(MSGAUD_user, 0, "javaembed: Type mismatch on result");
                 return JNIenv->CallBooleanMethod(result.l, getVal);