Memory Management for nsIScriptContext: Difference between revisions

From MozillaWiki
Jump to navigation Jump to search
(Mention XULElement::GetCompiledEventHandler() copies the void *)
(Move the event handler discussion to a new page)
Line 16: Line 16:
The only caller of this function (nsXULElement) does indeed ensure the void * returned is GC rooted.
The only caller of this function (nsXULElement) does indeed ensure the void * returned is GC rooted.


As opposed to the event handlers discussed next, there is no "implied reference" for this return object - so the lifetimes of these objects must be explicitly managed.
The reason for requiring this explicit management is that the result can not be assumed to have any other references (as opposed to Event Handlers, discussed later)
   
==Event Handlers==


Event handlers are a little trickier.  There are 3 steps to handling an event:
==EvaluateStringWithValue/CallEventHandler==


===Compile the script handler===
These functions are similar to CompileScript, but instead of a function object being returned, it is the result of calling the function is the issue.
   
This is similar in concept to CompileScript above - it can not be assumed any other references to the value exist, so the result must be explicitly managed.
   
Another alternative is that these functions will end up returning a language agnostic value, such as nsIVariant.  For example, the result of CallEventHandler is explicitly tested for a string, or for a null value - presumably these checks must still be made regardless of the language making the call.


Currently nsJSContext::CompileEventHandler does take the "event target" as a param - however, this is only used to setup principals, and does not otherwise "bind" the compiled function to the event target.  (Note: Brendan and I agreed that this param could be dropped from CompileEventHandler() to reinforce this lack of "binding", and the principals thing is apparently no longer necessary.  Note however that CompileScript *does* still take an nsIPrincipal param - is that still necessary?)
==Event Handlers==
 
Due to this lack of binding, the "void *" returned by this function is identical to CompileScript above - the callers must ensure these unbound handlers are rooted if they hold on to them - there is no "implied reference" setup.


===Bind the handler===
Event handlers are a little trickier.  In the process of "binding" a handler to an event target, an "implied reference" is created between the event target and the event handler.  Thus, in the current implementation, a bound event handler does not need to be rooted by callers.  Therefore, a case could be made that a bound event handler is a "borrowed reference" and requires no explicit cleanup.


This process binds the compiled event handler to a specific target.  In effect, this takes a copy of the compiled handler and "magically" associates it with the event target (see below).  After this process, the original compiled event handler is thrown away (and as the caller did not GCRoot the original, it will be disposed of by GC).  Critically however, this binding process has created an "implied reference" to the event target - once bound, other assumptions are then made that the event target will not be destroyed.
Either way, event handling is a subject in its own right (see [[Language Agnostic Event Handling]]), and more analysis is requiredHowever, the memory management issues for CompileScript etc are not related to event handlers, so can be resolved independentlyThat resolution then may, or may not, be applied to event handlers.
 
===Call the bound handler===
 
The "void *" passed to CallEventHandler is not the "void *" previously returned directly from CompileEventHandlerInstead, the "magic" association above is reversed, fetching a function object, by name, from the JS wrapper around the original event target.  Thus, currently the "void *" used is not supplied directly by nsIScriptContext, so the interface must be abstracted so it is.
 
==Notes:==
Theoretically, the above setup should mean that an event handler for a specific object only requires the original event target and event name to fire the event.  The "magic" association would ensure that when querying for the event name, the previously cloned function object will be located.  However, even though this should be possible, it does not happen for XUL, which stores its XPCWrappedNative pointer in its nsJSEventListener.  Thus, XUL is now storing a weak reference to a different interface, not the event itself, in the assumption neither will go away.  XBL appears to store the original target.
 
The nsJSEventListener only keeps a borrowed reference to the target.  Even though currently an XPCWrappedNative is stored in place of the original event, it is still a borrowed reference.  This borrowed reference itself relies on the "implied reference" model below - without that implied reference, the weak reference to the object in nsJSEventListener would fail.  Note also that all of this "WrapNative" work happens *outside* nsIScriptContext, so we need to work out how to abstract it behind that interface.
 
==The "implied reference"==
 
The above relies on an "implied reference" existing on the event target, via the "long-lived XPCWrappedNative" concept supported by XPConnect.  This mechanism is used by XPConnect tn ensure the same JS wrapper is always returned for a given DOM nsISupports.  While this model makes sense and all other languages would be encouraged to follow something similar, the nsIScriptContext model should not make any such implementation assumptionsOther languages may be able to perform the same magic using a different technique than XPConnect uses, and any "implied references" may not be true.  For example, other languages may choose to implement this with weak references to make their own ownership model simpler.


==Proposal==
==Proposal==


===Unbound function objects:===
===Unbound function objects:===
Unbound function objects (eg, returned by CompileScript, CompileEventHandler) should be managed by a simple unlock/lock memory management API:
Unbound function objects (eg, returned by CompileScript, CompileEventHandler), and "void *" return values should be managed by a simple unlock/lock memory management API:


* All such objects returned by nsIScriptContext are considered "locked".  They must remain locked while they are held externally. An 'unlock' function will be provided by the language to unlock the object.
* All such objects returned by nsIScriptContext are considered "locked".  They must remain locked while they are held externally. An 'unlock' function will be provided by the language to unlock the object.
Line 55: Line 42:
===Bound Event Handlers===
===Bound Event Handlers===


There are 2 options here - one relies on reusing the mechanism above for unbound functions, and the other relies on explicit xpcom strong references.
TBD
 
NOTE: I'm still looking at the existing code trying to come up with a concrete proposal.
 
====Have BindCompiledEventHandler() return an out void **====
 
This may be the simplest - this would return a new void **, which must be passed to CallEventHandler().  This new void * is subject to the same rules as unbound handlers, and must be individually unlocked.
 
Language implementations are then free to have this "void *" indirectly hold a strong reference to the event target (or in the case of JS, need only ensure that the wrapper persists)
 
====Formal binding objects====
 
The process of binding to an event handler can return a new nsISupports object. (As mentioned above, XUL already stores a different nsISupports pointer from the original event target - it just assumes that pointer has the same lifetime).  A strong reference to this returned object must be kept and the "binding" is only guaranteed to remain alive as long as the object.  Once the binding has been dropped, a new Binding can be created, but it is not guaranteed the returned nsISupports will be the same.
 
As this is a strong reference, it will be necessary to ensure these references are manually dropped.  This means that all nsJSEventListener objects must be explicitly dropped.  I believe this already happens now.


===Global Script Objects===
===Global Script Objects===


As mentioned at the start, it may (or may not) make sense to use the same explicit model we use for compiled scripts and event handlers.
As mentioned at the start, it may (or may not) make sense to use the same explicit model we use for compiled scripts and event handlers.

Revision as of 08:40, 15 September 2005

Script Object Lifetime Management

This discusses the Script Object lifetimes for all script objects returned by the nsIScriptContext interface. All such objects are returned as a "void *", and the lifetimes of these objects are implicitly tied up in the JS garbage collector and XPCWrappedNative lifetime management. This document attempts to explicitly define lifetime rules such that they can be implemented by any language (well, by JS and Python in the first instance)

Script Globals

A void * is obtained from the language for use as a "global" with an nsIScriptContext. Currently it is assumed the lifetime of the object is the same as the lifetime of the context itself. This model is reasonable and could be retained - however, if an alternate explicit mechanism is used in other places (see below), it may make sense to use that mechanism here.

CompileScript

CompileScript returns a "void *" for the script object. The interface comments for this function say:

   The caller is responsible for GC rooting this object
       

The only caller of this function (nsXULElement) does indeed ensure the void * returned is GC rooted.

The reason for requiring this explicit management is that the result can not be assumed to have any other references (as opposed to Event Handlers, discussed later)

EvaluateStringWithValue/CallEventHandler

These functions are similar to CompileScript, but instead of a function object being returned, it is the result of calling the function is the issue.

This is similar in concept to CompileScript above - it can not be assumed any other references to the value exist, so the result must be explicitly managed.

Another alternative is that these functions will end up returning a language agnostic value, such as nsIVariant. For example, the result of CallEventHandler is explicitly tested for a string, or for a null value - presumably these checks must still be made regardless of the language making the call.

Event Handlers

Event handlers are a little trickier. In the process of "binding" a handler to an event target, an "implied reference" is created between the event target and the event handler. Thus, in the current implementation, a bound event handler does not need to be rooted by callers. Therefore, a case could be made that a bound event handler is a "borrowed reference" and requires no explicit cleanup.

Either way, event handling is a subject in its own right (see Language Agnostic Event Handling), and more analysis is required. However, the memory management issues for CompileScript etc are not related to event handlers, so can be resolved independently. That resolution then may, or may not, be applied to event handlers.

Proposal

Unbound function objects:

Unbound function objects (eg, returned by CompileScript, CompileEventHandler), and "void *" return values should be managed by a simple unlock/lock memory management API:

  • All such objects returned by nsIScriptContext are considered "locked". They must remain locked while they are held externally. An 'unlock' function will be provided by the language to unlock the object.
  • It is likely that users of this interface may need to copy the returned object and manage the lifetimes seperately (for example, storing the returned "void *" in 2 unrelated datastructures). For this scenario, an explicit "lock()" call will be provided (meaning 2 unlock calls are now needed to finalize the object). However, as mentioned above, objects will always be returned locked so calling this will be rare. XULElement::GetCompiledEventHandler() is the only identified place that does this.

Bound Event Handlers

TBD

Global Script Objects

As mentioned at the start, it may (or may not) make sense to use the same explicit model we use for compiled scripts and event handlers.