I have attached a patch with the rewrite of JavaGetEnv() and
the threads cleanup. I like this approach a lot better
because now there is only one synchronization point,
inside the JavaSetupJava function. I think this will
take care of all of the possible race conditions
during initilization. The old JavaGetEnv method is
now called JavaInitEnv.

Jiang, what do you think? I will check it into the
contract branch if you give me the thumbs up on
this patch.

Mo DeJong
Red Hat Inc
Index: src/native/java.h
===================================================================
RCS file: /home/cvs/external/tcljava/src/native/java.h,v
retrieving revision 1.6.2.2
diff -u -r1.6.2.2 java.h
--- java.h      2000/08/07 00:50:09     1.6.2.2
+++ java.h      2000/08/08 07:17:41
@@ -140,7 +140,7 @@
 
 TCLBLEND_EXTERN void           JavaAlertNotifier();
 TCLBLEND_EXTERN void           JavaDisposeNotifier();
-TCLBLEND_EXTERN JNIEnv *       JavaGetEnv(Tcl_Interp *interp);
+TCLBLEND_EXTERN JNIEnv *       JavaGetEnv();
 TCLBLEND_EXTERN Tcl_Interp *   JavaGetInterp(JNIEnv *env, jobject interpObj);
 TCLBLEND_EXTERN char *         JavaGetString(JNIEnv *env, jstring str,
                                    int *lengthPtr);
Index: src/native/javaCmd.c
===================================================================
RCS file: /home/cvs/external/tcljava/src/native/javaCmd.c,v
retrieving revision 1.9.2.3
diff -u -r1.9.2.3 javaCmd.c
--- javaCmd.c   2000/08/07 08:50:16     1.9.2.3
+++ javaCmd.c   2000/08/08 07:17:42
@@ -10,7 +10,7 @@
  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  *
  *
- * RCS: @(#) $Id: javaCmd.c,v 1.9.2.3 2000/08/07 08:50:16 mo Exp $
+ * RCS: @(#) $Id: javaCmd.c,v 1.9.2.3 2000/08/07 00:50:09 mo Exp $
  */
 
 /*
@@ -46,6 +46,7 @@
 #include <stdlib.h>
 #include <stdarg.h>
 #include <errno.h>
+#include <assert.h>
 
 /*
  * Exported state variables.
@@ -94,6 +95,20 @@
 static int initialized_javaVM = 0;
 
 /*
+ * Declare a global mutex to protect the creation and initialization of the
+ * JVM from mutiple threads.  This mutex is used in conjunction with the
+ * 'initialized_javaVM' flag.  This mutex is used in javaCmd.c as well as
+ * javaInterp.c.
+ *
+ * FIXME: don't want to use the flag TCL_THREADS explicitly.  This may be
+ * better if in the future the same TclBlend binary can be made to work with
+ * both threaded and non-threaded Tcl libraries.  For now, we will use accessor
+ * functions lockJVMInitMutex() and unlockJVMInitMutex().
+ */
+TCL_DECLARE_MUTEX(javaVM_init_mutex)
+
+
+/*
  * The following array contains the class names and jclass pointers for
  * all of the classes used by this module.  It is used to initialize
  * the java structure's jclass slots.
@@ -179,6 +194,7 @@
  */
 
 static int             ToString(JNIEnv *env, Tcl_Obj *objPtr, jobject obj);
+static JNIEnv *                JavaInitEnv(JNIEnv *env, Tcl_Interp *interp);
 
 /*
  *----------------------------------------------------------------------
@@ -242,24 +258,11 @@
 #endif /* TCLBLEND_DEBUG */
 
     /*
-     * The first time JavaGetEnv is called, it will create a JVM.
-     * Later calls will just attach the current thread to the JVM.
+     * Init the JVM, the JNIEnv pointer, and any global data. Pass a
+     * NULL JNIEnv pointer to indicate Tcl Blend is being loaded from Tcl.
      */
-
-    if ((env = JavaGetEnv(interp)) == NULL) {
-
-#ifdef TCLBLEND_DEBUG
-    fprintf(stderr, "TCLBLEND_DEBUG: JavaGetEnv returned NULL\n");
-#endif /* TCLBLEND_DEBUG */
-
-       return TCL_ERROR;
-    }
 
-    /*
-     * Make sure the global VM information is properly intialized.
-     */
-
-    if (JavaSetupJava(env, interp) != TCL_OK) {
+    if (JavaSetupJava(NULL, interp) != TCL_OK) {
         return TCL_ERROR;
     }
 
@@ -267,6 +270,7 @@
      * Allocate a new Interp instance to wrap this interpreter.
      */
 
+    env = JavaGetEnv();
     *(Tcl_Interp**)&lvalue = interp;
     local = (*env)->NewObject(env, java.Interp,
            java.interpC, lvalue);
@@ -349,28 +353,59 @@
         vm_args.classpath, NULL);
 #endif
 }
+
 /*
  *----------------------------------------------------------------------
  *
  * JavaGetEnv --
  *
- *     Retrieve the JNI environment for the current thread.
+ *     Retrieve the JNI environment for the current thread. This method
+ *     is concurrent safe.
  *
  * Results:
- *     Returns the JNIEnv pointer for the current thread.  Returns
- *     NULL on error with a message left in the interpreter result.
+ *     Returns the JNIEnv pointer for the current thread.
+ *     This method  must be called after JavaInitEnv has been called.
  *
  * Side effects:
- *     May create or attach to a new JavaVM if this is the first time
- *     the JNIEnv is fetched from outside of a Java callback.
  *
  *----------------------------------------------------------------------
  */
 
 TCLBLEND_EXTERN JNIEnv *
-JavaGetEnv(
-    Tcl_Interp *interp)                /* Interp for error reporting. */
+JavaGetEnv()
 {
+    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
+
+    assert(tsdPtr->initialized_currentEnv);
+
+    return tsdPtr->currentEnv;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * JavaInitEnv --
+ *
+ *     Init the JNIEnv for this thread.
+ *
+ * Results:
+ *     Returns the JNIEnv pointer for the current thread.  Returns
+ *     NULL on error with a message left in the interpreter result.
+ *
+ * Side effects:
+ *     If Tcl Blend is loaded into Tcl and this is the first thread
+ *     to load Tcl Blend, a new JVM will be created. If another
+ *     Tcl thread loads Tcl Blend, that thread will be attached to
+ *     the existing JVM.
+ *----------------------------------------------------------------------
+ */
+
+JNIEnv *
+JavaInitEnv(
+    JNIEnv *env,        /* JNIEnv pointer, NULL if loaded from Tcl Blend */
+    Tcl_Interp *interp /* Interp for error reporting. */
+)
+{
     jsize nVMs;
     char *path, *newPath;
     int oldSize, size, maxOptions = 2;
@@ -385,14 +420,8 @@
 
     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
 
-    if (tsdPtr->initialized_currentEnv) {
-       return tsdPtr->currentEnv;
-    }
-
-    /* FIXME  : we need to put a mutex around this create JVM/attach step */
-
 #ifdef TCLBLEND_DEBUG
-    fprintf(stderr, "TCLBLEND_DEBUG: JavaGetEnv for %s JVM\n",
+    fprintf(stderr, "TCLBLEND_DEBUG: JavaInitEnv for %s JVM\n",
 #ifdef JDK1_2
         "JDK1_2"
 #elif defined TCLBLEND_KAFFE
@@ -404,10 +433,34 @@
 #endif /* TCLBLEND_DEBUG */
 
     /*
-     * Check to see if the current process already has a Java VM.  If so,
-     * attach to it, otherwise create a new one.
+     * If init was already called for this thread, do nothing.
+     * This can happen if multiple interpreters are created in
+     * the same thread.
      */
 
+    if (tsdPtr->initialized_currentEnv) {
+        return tsdPtr->currentEnv;
+    }
+
+    /*
+     * If we were called with a non-NULL JNIEnv argument, it means
+     * Tcl Blend was loaded from Java. In this case, the JNIEnv is
+     * already attached to the JVM because it was created in Java.
+     * Since we do not need to create a JVM and we do not need to
+     * attach the current thread, we just set currentEnv and return.
+     */
+
+    if (env) {    
+        tsdPtr->initialized_currentEnv = 1;
+        return (tsdPtr->currentEnv = env);
+    }
+
+    /*
+     * From this point on, deal with the case where Tcl Blend is loaded from Tcl.
+     * Check to see if the current process already has a Java VM.  If so, attach
+     * the current thread to it, otherwise create a new JVM (automatic thread 
+attach).
+     */
+
     if (JNI_GetCreatedJavaVMs(&javaVM, 1, &nVMs) < 0) {
 
 #ifdef TCLBLEND_DEBUG
@@ -415,7 +468,7 @@
 #endif /* TCLBLEND_DEBUG */
 
        Tcl_AppendResult(interp, "JNI_GetCreatedJavaVMs failed", NULL);
-       return NULL;
+       goto error;
     }
 
     if (nVMs == 0) {
@@ -523,7 +576,7 @@
     "Tcl Blend only: If the value is 'help', then JVM initialization stop\n",
     "and this message is returned.",
                                 NULL);
-                return NULL;
+                goto error;
             }
            options[vm_args.nOptions].extraInfo = (void *)NULL;
             vm_args.nOptions++;
@@ -568,7 +621,7 @@
                                      "than the one Tcl Blend was compiled with?\n", 
                                      NULL);
             appendClasspathMessage(interp);
-           return NULL;
+            goto error;
        }
 
     } else {
@@ -589,12 +642,24 @@
 #endif /* TCLBLEND_DEBUG */
 
            Tcl_AppendResult(interp, "AttachCurrentThread failed", NULL);
-           return NULL;
+           goto error;
        }
     }
 
+#ifdef TCLBLEND_DEBUG
+    fprintf(stderr, "TCLBLEND_DEBUG: JavaInitEnv returning successfully\n");
+#endif /* TCLBLEND_DEBUG */
+
     tsdPtr->initialized_currentEnv = 1;
     return tsdPtr->currentEnv;
+
+    error:
+
+#ifdef TCLBLEND_DEBUG
+    fprintf(stderr, "TCLBLEND_DEBUG: JavaInitEnv returning NULL\n");
+#endif /* TCLBLEND_DEBUG */
+
+    return NULL;
 }
 
 /*
@@ -688,7 +753,7 @@
     Tcl_Interp *interp)                /* Interpreter being deleted. */
 {
     jobject interpObj = (jobject) clientData;
-    JNIEnv *env = JavaGetEnv(interp);
+    JNIEnv *env = JavaGetEnv();
 
     /*
      * Set the Interp.interpPtr field to 0 so any further attempts to use
@@ -705,7 +770,7 @@
     (*env)->CallVoidMethod(env, interpObj, java.dispose);
     (*env)->DeleteGlobalRef(env, interpObj);
 
-    /* FIXME : detach the JNIEnv */
+    /* FIXME : detach the JNIEnv, but only if this is the last interp in the thread 
+??? */
 
 #ifdef TCLBLEND_DEBUG
     fprintf(stderr, "TCLBLEND_DEBUG: called JavaInterpDeleted\n");
@@ -719,7 +784,11 @@
  *
  * JavaSetupJava --
  *
- *     Set up the cache of class and method ids.
+ *     This is the entry point for a Tcl interpreter created from Java.
+ *     This method will save the JVM JNIEnv pointer by calling JavaInitEnv
+ *     if this was the first time JavaSetupJava was called for the current
+ *     thread. It will also set up the cache of class and method ids if this
+ *     was the first time JavaSetupJava was called in this process.
  *
  * Results:
  *     A standard Tcl result.
@@ -739,15 +808,33 @@
     jfieldID field;
     int i;
 
-
 #ifdef TCLBLEND_DEBUG
     fprintf(stderr, "TCLBLEND_DEBUG: called JavaSetupJava\n");
 #endif /* TCLBLEND_DEBUG */
 
+    /*
+     * Acquire the init lock, we do not care if it is slow to call
+     * JavaSetupJava, it is only called when an Interp is created.
+     */
+
+    Tcl_MutexLock(&javaVM_init_mutex);
+
+    /*
+     * Check that the JNIEnv for this thread has been initialized.
+     * If Tcl Blend is getting loaded from Tcl, then the env argument
+     * would be passed as NULL.
+     */
+
+    if ((env = JavaInitEnv(env, interp)) == NULL) {
+       goto error;
+    }
+
+    /*
+     * If the global java struct was already initialized, just return
+     */
 
-    /* FIXME : we need to put a mutex around this test/set */
     if (initialized_javaVM) {
-       return TCL_OK;
+        goto ok;
     }
     
     memset(&java, 0, sizeof(java));
@@ -766,7 +853,8 @@
                 (*env)->ExceptionDescribe(env);
                 obj = Tcl_GetObjResult(interp);
                 (*env)->ExceptionClear(env);
-                /* We can't call ToString() here, we might not have
+                /*
+                * We can't call ToString() here, we might not have
                  * java.toString yet.
                  */
                 (*env)->Throw(env, exception);
@@ -858,9 +946,21 @@
     JavaObjInit();
 
     initialized_javaVM = 1;
+
+    ok:
+#ifdef TCLBLEND_DEBUG
+    fprintf(stderr, "TCLBLEND_DEBUG: JavaSetupJava returning successfully\n");
+#endif /* TCLBLEND_DEBUG */
+
+    Tcl_MutexUnlock(&javaVM_init_mutex);
     return TCL_OK;
 
     error:
+#ifdef TCLBLEND_DEBUG
+    fprintf(stderr, "TCLBLEND_DEBUG: JavaSetupJava returning TCL_ERROR\n");
+#endif /* TCLBLEND_DEBUG */
+
+    Tcl_MutexUnlock(&javaVM_init_mutex);
     (*env)->ExceptionClear(env);
     return TCL_ERROR;
 }
Index: src/native/javaIdle.c
===================================================================
RCS file: /home/cvs/external/tcljava/src/native/javaIdle.c,v
retrieving revision 1.2.2.1
diff -u -r1.2.2.1 javaIdle.c
--- javaIdle.c  2000/07/30 07:17:09     1.2.2.1
+++ javaIdle.c  2000/08/08 07:17:42
@@ -118,7 +118,7 @@
 JavaIdleProc(
     ClientData clientData)     /* Global IdleHandler reference */
 {
-    JNIEnv *env = JavaGetEnv(NULL);
+    JNIEnv *env = JavaGetEnv();
     jobject exception;
     jobject idle = (jobject) clientData;
 
Index: src/native/javaInterp.c
===================================================================
RCS file: /home/cvs/external/tcljava/src/native/javaInterp.c,v
retrieving revision 1.7.2.2
diff -u -r1.7.2.2 javaInterp.c
--- javaInterp.c        2000/08/07 00:50:09     1.7.2.2
+++ javaInterp.c        2000/08/08 07:17:43
@@ -823,7 +823,7 @@
     jstring name1Str, name2Str;
     jobject exception, interpObj;
     Tcl_SavedResult state;
-    JNIEnv *env = JavaGetEnv(interp);
+    JNIEnv *env = JavaGetEnv();
 
     result = NULL;
     if (tPtr->errMsg != NULL) {
@@ -953,7 +953,7 @@
     ClientData clientData)
 {
     jobject cmd = (jobject)clientData;
-    JNIEnv *env = JavaGetEnv(NULL);
+    JNIEnv *env = JavaGetEnv();
 
     if ((*env)->IsInstanceOf(env, cmd, java.CommandWithDispose)) {
        (*env)->CallVoidMethod(env, cmd, java.disposeCmd);
@@ -990,7 +990,7 @@
     jarray args;
     jobject value, exception, interpObj;
     int i, result;
-    JNIEnv *env = JavaGetEnv(interp);
+    JNIEnv *env = JavaGetEnv();
 
     interpObj = (jobject) Tcl_GetAssocData(interp, "java", NULL);
 
Index: src/native/javaNotifier.c
===================================================================
RCS file: /home/cvs/external/tcljava/src/native/javaNotifier.c,v
retrieving revision 1.2.2.1
diff -u -r1.2.2.1 javaNotifier.c
--- javaNotifier.c      2000/07/30 07:17:09     1.2.2.1
+++ javaNotifier.c      2000/08/08 07:17:43
@@ -171,7 +171,7 @@
     ClientData data,           /* Not used. */
     int flags)                 /* Same as for Tcl_DoOneEvent. */
 {
-    JNIEnv *env = JavaGetEnv(NULL);
+    JNIEnv *env = JavaGetEnv();
 
     if ((*env)->CallBooleanMethod(env, globalNotifierObj, java.hasEvents)
            == JNI_TRUE) {
@@ -202,7 +202,7 @@
     ClientData data,           /* Not used. */
     int flags)                 /* Same as for Tcl_DoOneEvent. */
 {
-    JNIEnv *env = JavaGetEnv(NULL);
+    JNIEnv *env = JavaGetEnv();
     Tcl_Event *ePtr;
 
     /*
@@ -241,7 +241,7 @@
     Tcl_Event *evPtr,          /* The event that is being processed. */
     int flags)                 /* The flags passed to Tcl_ServiceEvent. */
 {
-    JNIEnv *env = JavaGetEnv(NULL);
+    JNIEnv *env = JavaGetEnv();
     
     /*
      * Call Notifier.serviceEvent() to handle invoking the next event and
Index: src/native/javaObj.c
===================================================================
RCS file: /home/cvs/external/tcljava/src/native/javaObj.c,v
retrieving revision 1.4.2.1
diff -u -r1.4.2.1 javaObj.c
--- javaObj.c   2000/07/30 07:17:09     1.4.2.1
+++ javaObj.c   2000/08/08 07:17:43
@@ -128,7 +128,7 @@
     Tcl_Obj *destPtr)
 {
     jobject object = (jobject)(srcPtr->internalRep.twoPtrValue.ptr2);
-    JNIEnv *env = JavaGetEnv(NULL);
+    JNIEnv *env = JavaGetEnv();
 
     /*
      * Add a global reference to represent the new copy.
@@ -162,7 +162,7 @@
     Tcl_Obj *objPtr)           /* Object to free. */
 {
     jobject object = (jobject)(objPtr->internalRep.twoPtrValue.ptr2);
-    JNIEnv *env = JavaGetEnv(NULL);
+    JNIEnv *env = JavaGetEnv();
 
     (*env)->CallVoidMethod(env, object, java.release);
     (*env)->DeleteGlobalRef(env, object);
@@ -218,7 +218,7 @@
 {
     jstring string;
     jobject object = (jobject)(objPtr->internalRep.twoPtrValue.ptr2);
-    JNIEnv *env = JavaGetEnv(NULL);
+    JNIEnv *env = JavaGetEnv();
 
     string = (*env)->CallObjectMethod(env, object, java.toString);
     objPtr->bytes = JavaGetString(env, string, &objPtr->length);
@@ -704,7 +704,7 @@
     } else if ((objPtr->typePtr == cmdTypePtr)
            && (objPtr->internalRep.twoPtrValue.ptr2 != NULL)) {
        jobject object = (jobject)(objPtr->internalRep.twoPtrValue.ptr2);
-       JNIEnv *env = JavaGetEnv(NULL);
+       JNIEnv *env = JavaGetEnv();
 
        /*
         * If we are converting from something that is already a java command
Index: src/native/javaTimer.c
===================================================================
RCS file: /home/cvs/external/tcljava/src/native/javaTimer.c,v
retrieving revision 1.2.2.1
diff -u -r1.2.2.1 javaTimer.c
--- javaTimer.c 2000/07/30 07:17:09     1.2.2.1
+++ javaTimer.c 2000/08/08 07:17:43
@@ -114,7 +114,7 @@
     ClientData clientData)     /* Pointer to TimerInfo. */
 {
     TimerInfo *infoPtr = (TimerInfo *) clientData;
-    JNIEnv *env = JavaGetEnv(NULL);
+    JNIEnv *env = JavaGetEnv();
     jobject exception;
 
     /*

Reply via email to