Boy, I must have been on crack yesterday. The patch I posted that used a global table to keep a legal ref to a cleanup object was way way too complex. After rereading section 12.6.1 of the JLS, I realized the error of my ways. http://java.sun.com/docs/books/jls/second_edition/html/execution.doc.html#44760 When the finalize() method gets invoked, it moves the object getting finalized back into a reachable state. unreachable -> f-reachable -> reachable -> finalize() -> unreachable Then after the finalize() method has been run it transitions to the unreachable state and it later cleaned up. That means that while the finalize() method is getting run, all the objects it contains are reachable, so we can "rescue" a member object. What that means, is that in our CObject class, we can just keep a ref to a cleanup object in the CObject instance and then append it to the queue in the finalize() method. No external table is required. Needless to say, this makes things much much simpler. Now all we need to do is create a cleanup object in the CObject constructor, and then queue it up in the finalize() method. CObject( long objPtr) // Pointer to Tcl_Obj from C. { this.objPtr = objPtr; incrRefCount(objPtr); notifier = Notifier.getNotifierForThread(Thread.currentThread()); cleanup = new CleanupCallback(); } protected void finalize() throws Throwable { if (objPtr != 0) { /* * If a CObject is finalized while the reference is still valid, * we need to send the reference back to the thread that created it. * We can then sever the connection to the underlying Tcl_Obj* by * calling decrRefCount() in that thread. Note that we can not wait * for the event to be processed, as it would block the GC thread. */ cleanup.objPtr = objPtr; notifier.queueEvent(cleanup, TCL.QUEUE_TAIL); } super.finalize(); } The patch that implements this change is attached to this email. Folks should try this patch out instead of the one I posted yesteday. I think this implementation is much better, but there is one more thing we could try to avoid the extra object in the CObject. It might be possible to turn the CObject into a TclEvent and queue that up. I am not sure if this is legal so I am going to have to read the spec a bit more. It would mean that it would have to be possible to invoke a method on the object after the finalize() method had been run. cheers Mo DeJong Red Hat Inc
Index: CObject.java =================================================================== RCS file: /cvsroot/tcljava/tcljava/src/tclblend/tcl/lang/CObject.java,v retrieving revision 1.1.1.1 diff -u -r1.1.1.1 CObject.java --- CObject.java 1998/10/14 21:09:10 1.1.1.1 +++ CObject.java 2000/10/22 23:19:01 @@ -24,14 +24,22 @@ class CObject extends InternalRep { -/* - * This long really contains a Tcl_Obj*. It is declared with package - * visibility so that subclasses that define type specific functionality - * can get to the Tcl_Obj. - */ +// This long really contains a Tcl_Obj*. It is declared with package +// visibility so that subclasses that define type specific functionality +// can get to the Tcl_Obj. This field can be read from C code. long objPtr; +// A cleanup instance is stored in each CObject so that in the case +// where a dangling reference to a Tcl_Obj* is found, an event +// can be queued to free up the dangling pointer in the original thread. + +CleanupCallback cleanup; + +// The notifier for the thread that created this CObject. + +Notifier notifier; + /* *---------------------------------------------------------------------- @@ -51,8 +59,7 @@ CObject() { - this.objPtr = newCObject(null); - incrRefCount(objPtr); + this(newCObject(null)); } /* @@ -76,6 +83,10 @@ { this.objPtr = objPtr; incrRefCount(objPtr); + + notifier = Notifier.getNotifierForThread(Thread.currentThread()); + + cleanup = new CleanupCallback(); } /* @@ -97,6 +108,7 @@ public void dispose() { + cleanup = null; decrRefCount(objPtr); objPtr = 0; } @@ -180,6 +192,7 @@ * newInstance -- * * Construct a new TclObject from a Tcl_Obj*. This routine is only + * called from C. It is also the only CObject method that can be * called from C. * * Results: @@ -218,11 +231,19 @@ { if (objPtr != 0) { /* - * If the object is finalized while the reference is still valid, - * we need to sever the connection to the underlying Tcl_Obj*. + * If a CObject is finalized while the reference is still valid, + * we need to send the reference back to the thread that created it. + * We can then sever the connection to the underlying Tcl_Obj* by + * calling decrRefCount() in that thread. Note that we can not wait + * for the event to be processed, as it would block the GC thread. */ - decrRefCount(objPtr); +//FIXME: see if it would be possible to put the this object itself +// back onto the queue, thus making it reachable again????? +// we could then implement TclEvent on CObject and reduce mem use. + cleanup.objPtr = objPtr; + //System.err.println("queueing cleanup for " + cleanup.objPtr); + notifier.queueEvent(cleanup, TCL.QUEUE_TAIL); } super.finalize(); } @@ -230,6 +251,34 @@ /* *---------------------------------------------------------------------- * + * CleanupCallback -- + * + * This is a little helper class used to store the objPtr + * and to take on the role of the TclEvent that will be + * called after the event is queued. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static class CleanupCallback extends TclEvent { + long objPtr; + + public int processEvent(int flags) { + //System.err.println("calling decrRefCount from processEvent for " + objPtr); + CObject.decrRefCount(objPtr); + return 1; + } +} + +/* + *---------------------------------------------------------------------- + * * decrRefCount -- * * Decrement the refcount of a Tcl_Obj. @@ -282,7 +331,6 @@ * *---------------------------------------------------------------------- */ - static final native long newCObject(