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;
/*