I took another pass at the thread cache cleanup
patch, and I think this one is a lot better.
It now includes code to detach from the JVM
even in the "main" thread. This only works
in JDK 1.2 and it only matters for the thread
that created the JVM, but it is an improvement.

I tried to use the JNI->DestroyJavaVM method,
but it is so chuck full of bugs that there
really is no point in calling it at this time.
JDK 1.2 added the ability to detach the main
thread without calling DestroyJavaVM() so
it really does not matter too much.

Mo DeJong
Red Hat Inc
Index: javaCmd.c
===================================================================
RCS file: /cvsroot/tcljava/tcljava/src/native/javaCmd.c,v
retrieving revision 1.9.2.7
diff -u -r1.9.2.7 javaCmd.c
--- javaCmd.c   2000/08/27 08:07:40     1.9.2.7
+++ javaCmd.c   2000/10/21 14:52:46
@@ -103,7 +103,9 @@
                             char *name, jclass *class, char *sig, int isStatic);
 static int             AddToFieldCache(JNIEnv *env, Tcl_Interp *interp, jfieldID 
*addr,
                             char *name, jclass *class, char *sig);
-static void            TclThreadCleanup(ClientData clientData);
+static void            DestroyJVM(ClientData clientData);
+static void            DetachTclThread(ClientData clientData);
+static void            FreeJavaCache(ClientData clientData);
 
 /*
  *----------------------------------------------------------------------
@@ -558,6 +560,10 @@
             goto error;
        }
 
+        /* Create a thread exit handler that will destroy the JVM */
+
+       Tcl_CreateThreadExitHandler(DestroyJVM, NULL);
+
     } else {
 
 #ifdef TCLBLEND_DEBUG
@@ -581,7 +587,7 @@
        
        /* Create a thread exit handler to detach this Tcl thread from the JVM */
        
-       Tcl_CreateThreadExitHandler(TclThreadCleanup, NULL);
+       Tcl_CreateThreadExitHandler(DetachTclThread, NULL);
     }
 
 #ifdef TCLBLEND_DEBUG
@@ -717,11 +723,12 @@
 /*
  *----------------------------------------------------------------------
  *
- * TclThreadCleanup --
+ * DestroyJVM --
  *
- *     This method will be called when a Tcl thread is getting finalized.
- *     It needs to detach the Tcl thread from the current JVM, note that
- *     this method is not called for a Java thread that loaded Tcl Blend.
+ *     This method will be called when the "main" Tcl thread (the
+ *     one that first loaded the JVM) is getting finalized.
+ *     Note that this method would never get called in the case
+ *     where Tcl Blend is loaded into an existing JVM.
  *
  * Results:
  *     None.
@@ -732,22 +739,138 @@
  *----------------------------------------------------------------------
  */
 static void
-TclThreadCleanup(ClientData clientData)
+DestroyJVM(ClientData clientData)
 {
-    JNIEnv* env;
     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
 
 #ifdef TCLBLEND_DEBUG
-    fprintf(stderr, "TCLBLEND_DEBUG: called TclThreadCleanup\n");
+    fprintf(stderr, "TCLBLEND_DEBUG: called DestroyJVM\n");
 #endif /* TCLBLEND_DEBUG */
+
+    /*
+     * The DestroyJavaVM() JNI method has never worked
+     * properly, and in some cases it can even crash
+     * your system. For this reason, we do not actually
+     * call it here. One of the enhancements made to
+     * JNI in 1.2 was to support detaching of the thread
+     * that created the JVM, so call DetachTclThread.
+     */
+
+#ifdef JDK1_2
+     DetachTclThread(NULL);
+#else
+    /*
+     * This method always returns an error. It does nothing
+     * and can generate an illegal instruction if called
+     * from a second Tcl thread (the "main" thread in 1.1)
+     */
+    /* (*javaVM)->DestroyJavaVM(javaVM); */
+#endif
 
-    env = tsdPtr->currentEnv;
-    (*javaVM)->DetachCurrentThread(javaVM);
+    /*
+     * One should not call JavaGetEnv() or JavaGetCache()
+     * after this method has finished. Setting initialized
+     * to zero will raise an assert on such a call.
+     */
+
+    tsdPtr->initialized = 0;
 }
 
 /*
  *----------------------------------------------------------------------
  *
+ * DetachTclThread --
+ *
+ *     This method will be called when a Tcl thread that was attached
+ *     to a JVM is getting finalized. Note that this method is not
+ *     called for a thread that originated in the JVM, because the
+ *     JVM would handle attaching and detaching of such a thread.
+ *     Also note that this method is not called for the "main"
+ *     thread, meaning the Tcl thread that first loaded the JVM.
+ *
+ * Results:
+ *     None.
+ *
+ * Side effects:
+ *     None.
+ *
+ *----------------------------------------------------------------------
+ */
+static void
+DetachTclThread(ClientData clientData)
+{
+    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
+
+#ifdef TCLBLEND_DEBUG
+    fprintf(stderr, "TCLBLEND_DEBUG: called DetachTclThread\n");
+#endif /* TCLBLEND_DEBUG */
+
+    if ((*javaVM)->DetachCurrentThread(javaVM) != 0) {
+#ifdef TCLBLEND_DEBUG
+    fprintf(stderr, "TCLBLEND_DEBUG: error calling jvm->DetachCurrentThread()\n");
+#endif /* TCLBLEND_DEBUG */
+    }
+
+    /*
+     * After detaching this Tcl thread from the JVM, one can not
+     * call JavaGetEnv() or JavaGetCache(). Setting initialized
+     * to zero will raise an assert on such a call.
+     */
+
+    tsdPtr->initialized = 0;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * FreeJavaCache --
+ *
+ *     This method will be called when a Tcl or Java thread is finished.
+ *     It needs to remove any global cache references so that the
+ *     classes and methods can be cleaned up by the JVM.
+ *
+ * Results:
+ *     None.
+ *
+ * Side effects:
+ *     None.
+ *
+ *----------------------------------------------------------------------
+ */
+static void
+FreeJavaCache(ClientData clientData)
+{
+    JNIEnv* env = JavaGetEnv();
+    JavaInfo* jcache = JavaGetCache();
+
+#ifdef TCLBLEND_DEBUG
+    fprintf(stderr, "TCLBLEND_DEBUG: called FreeJavaCache\n");
+#endif /* TCLBLEND_DEBUG */
+
+    /* We need to delete any global refs to Java classes */
+    
+    (*env)->DeleteGlobalRef(env, jcache->Object);
+    (*env)->DeleteGlobalRef(env, jcache->Interp);
+    (*env)->DeleteGlobalRef(env, jcache->Command);
+    (*env)->DeleteGlobalRef(env, jcache->TclObject);
+    (*env)->DeleteGlobalRef(env, jcache->TclException);
+    (*env)->DeleteGlobalRef(env, jcache->CommandWithDispose);
+    (*env)->DeleteGlobalRef(env, jcache->CObject);
+    (*env)->DeleteGlobalRef(env, jcache->Extension);
+    (*env)->DeleteGlobalRef(env, jcache->VarTrace);
+    (*env)->DeleteGlobalRef(env, jcache->Void);
+    (*env)->DeleteGlobalRef(env, jcache->BlendExtension);
+    (*env)->DeleteGlobalRef(env, jcache->Notifier);
+    (*env)->DeleteGlobalRef(env, jcache->IdleHandler);
+    (*env)->DeleteGlobalRef(env, jcache->TimerHandler);
+
+     /* FIXME : we dont add or release a global ref for jcache->Void
+        or jcache->VoidTYPE class, should we ? */
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
  * JavaSetupJava --
  *
  *     This is the entry point for a Tcl interpreter created from Java.
@@ -830,6 +953,19 @@
     }
 
     /*
+     * Get the Void.TYPE class.
+     */
+
+    field = (*env)->GetStaticFieldID(env, jcache->Void, "TYPE", "Ljava/lang/Class;");
+    jcache->voidTYPE = (*env)->GetStaticObjectField(env, jcache->Void, field);
+
+    /*
+     * Create a thread exit handler that will clean up the cache.
+     */
+
+    Tcl_CreateThreadExitHandler(FreeJavaCache, NULL);
+
+    /*
      * Load methods needed by this module.
      */
 
@@ -881,13 +1017,6 @@
         AddToFieldCache(env, interp, &jcache->objPtr, "objPtr", &jcache->CObject, 
"J")) {
        goto error;
     }
-
-    /*
-     * Get the Void.TYPE value.
-     */
-
-    field = (*env)->GetStaticFieldID(env, jcache->Void, "TYPE", "Ljava/lang/Class;");
-    jcache->voidTYPE = (*env)->GetStaticObjectField(env, jcache->Void, field);
 
     /*
      * Register the Java object types.

Reply via email to