Memory Management for nsIScriptContext: Difference between revisions

From MozillaWiki
Jump to navigation Jump to search
(Move the event handler discussion to a new page)
 
(9 intermediate revisions by 7 users not shown)
Line 1: Line 1:
=Script Object Lifetime Management=
=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)
The problem:


==Script Globals==
Certain nsIScriptContext methods return a |void *| tied to the specific language.  For example, |CompileEventHandler| returns a "function" object which is held by the caller and later passed to |CallEventHandler|.  The lifetime of these returned |void *| objects must be managed.


At the high-level, it has been agreed that new methods |DropScriptObject| and |HoldScriptObject| will be added to certain language-specific interfaces.  The affected |nsIScriptContext| methods will return a 'held' object; the caller must call |DropScriptObject| when it no longer needs to store it.  |HoldScriptObject| is provided so that copies of the objects can be made.  This is identical to a reference counting scheme, but named to avoid confusion with XPCOMs reference counting.


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.
We need to turn this into an actual implementation, with the following restrictions:
   
==CompileScript==


CompileScript returns a "void *" for the script object.  The interface comments for this function say:
* We want to avoid additional memory allocation or virtual method calls.  The only overhead the Drop/Hold scheme requires is one additional virtual-method call for the memory management function itself.


    The caller is responsible for GC rooting this object
* We can not have the memory management functions exclusively on nsIScriptContext.  There is code that currently stores a |void *| script object, but has no nsIScriptContext available.  One example is nsJSEventListener, which stores the |void *|, but has the |nsIScriptContext| provided as part of invoking the event.  nsJSEventListener would not want to store an nsIScriptContext just to free the script object.  Another example is the XUL cache which will be extended to cache all languages, but will not want to store an nsIScriptContext just to handle cache shutdown.
       
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)
[http://hostgator1centcoupon.net/ Hostgator VPS Coupon]
[http://hostgator-reseller-coupon.com/ Hostgator Reseller Coupon]
[http://hostgator-vps-coupon.net/ Hostgator 1 cent coupon]


==EvaluateStringWithValue/CallEventHandler==
* Linkage issues mean that "helper functions" are unsuitable for use here.  As the DOM implementation and each language implementation are individual components, public static functions are not globally reachable.  Either xpcom interfaces or fully inline C++ classes can be used to assist with these tasks.


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.
== Implementation idea #1 ==
   
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==
Add DropScriptObect/HoldScriptObject to nsILanguageRuntime (a new class - a 'singleton' for each language - it creates nsIScriptContext objects amongst other things).  Code then only needs an integer language ID to be able to free the script object.


Event handlers are a little trickierIn 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.  
A downside of this approach is that all memory management functions take an extra penaltyEach function involves:
* Fetching the nsDOMScriptObjectFactory service.
* Fetching the nsILanguageRuntime from the nsDOMScriptObjectFactory passing the int langID.
* Calling the nsILanguageRuntime::Drop/HoldScriptObject function.


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.
This problem could be solved by either:
* Duplicating these functions on nsIScriptContext, so code that does have a context available can do so without the additional overhead.
* Having performance critical code keep a reference to the nsILanguageRuntime.


==Proposal==
Even still, a key problem to this approach remains its fragility.  By explicitly requiring manual Drop/Hold calls, there is a lot of scope for programmer error in the face of error conditions (ie, early returns) to cause difficult to track leaks.  The inevitability of such errors is evidenced by nsCOMPtr and various other tricks already in the code base.


===Unbound function objects:===
== Implementation idea #2 ==
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.
This implementation builds on #1 as its core framework, and attempts to add a "helper class" (nsScriptObjectHolder) to automate the management of these objectsAll nsIScriptContext methods that return a "new" script object now take a reference to one of these objects.
* 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===
The public prototype for this class is:


TBD
class nsScriptObjectHolder {
public:
    nsScriptObjectHolder();
    nsScriptObjectHolder(PRUint32 aLangID, void *aObject);
    nsScriptObjectHolder(const nsScriptObjectHolder& other);
    ~nsScriptObjectHolder();
    nsScriptObjectHolder &operator=(const nsScriptObjectHolder &other);
    PRBool operator!() const;
    operator void *() const;
    nsresult set(PRUint32 langID, void *object);
    nsresult set(void *object);
    nsresult set(const nsScriptObjectHolder &other);
    nsresult clear();
    PRUint32 mLangID;
};


===Global Script Objects===
the nsIScriptContext methods changed are CompileScript, CompileEventHandler, GetBoundEventHandler, CompileFunction and Deserialize, with all changes similar to:


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.
    virtual nsresult GetBoundEventHandler(nsISupports* aTarget, void *aScope,
                                          nsIAtom* aName,
-                                        void** aHandler) = 0;
+                                        nsScriptObjectHolder &aHandler) = 0;
 
 
Code calling these methods changes from:
  void *handler = nsnull;
  rv = context->GetBoundEventHandler(..., &handler);
  if (handler)
    context->CallEventHandler(..., handler)
 
to:
 
  nsScriptObjectHolder handler;
  rv = context->GetBoundEventHandler(..., handler);
  if (handler)
    context->CallEventHandler(..., handler)
 
The use of "operator void *" and "operator !" means that much existing code that uses |void *| can remain unchanged once the variable has changed to nsScriptObjectHolder.  For example, an nsScriptObjectHolder can be passed to nsIScriptContext::CallEventHandler, even though the prototype remains |void *|.
 
=== Problems ===
 
* The nsScriptObjectHolder class must have a fully inline implementation.  Any attempt to use methods defined in gklayout.dll mean the class can not be used by external languages (such as Python).
 
* This does not lend itself to allowing DropScriptObject/HoldScriptObject on both nsIScriptContext and nsILanguageRuntime.  As not all callers can use nsIScriptContext, it must be nsILanguageRuntime.  This means each memory management function is slightly expensive, as detailed above.
 
== Implementation idea #3 ==
 
Use a real XPCOM interface.  Something like:
 
interface nsIScriptObjectHolder : public nsISupports
{
    virtual void *GetScriptObject();
}
 
and all nsIScriptContext methods return one of these interfaces instead of a |void *|.  The memory management is then implicitly tied up in the lifetime of the interface itself.
 
=== Problems ===
* Fetching the |void *| language object becomes a virtual method.
* Each call requires a new allocation for the interface.
 
== Notes ==
 
Implementation idea #2 has been implemented and works well.  However, concerns exist regarding the performance overhead in terms of speed and bloat.

Latest revision as of 18:47, 18 April 2012

Script Object Lifetime Management

The problem:

Certain nsIScriptContext methods return a |void *| tied to the specific language. For example, |CompileEventHandler| returns a "function" object which is held by the caller and later passed to |CallEventHandler|. The lifetime of these returned |void *| objects must be managed.

At the high-level, it has been agreed that new methods |DropScriptObject| and |HoldScriptObject| will be added to certain language-specific interfaces. The affected |nsIScriptContext| methods will return a 'held' object; the caller must call |DropScriptObject| when it no longer needs to store it. |HoldScriptObject| is provided so that copies of the objects can be made. This is identical to a reference counting scheme, but named to avoid confusion with XPCOMs reference counting.

We need to turn this into an actual implementation, with the following restrictions:

  • We want to avoid additional memory allocation or virtual method calls. The only overhead the Drop/Hold scheme requires is one additional virtual-method call for the memory management function itself.
  • We can not have the memory management functions exclusively on nsIScriptContext. There is code that currently stores a |void *| script object, but has no nsIScriptContext available. One example is nsJSEventListener, which stores the |void *|, but has the |nsIScriptContext| provided as part of invoking the event. nsJSEventListener would not want to store an nsIScriptContext just to free the script object. Another example is the XUL cache which will be extended to cache all languages, but will not want to store an nsIScriptContext just to handle cache shutdown.

Hostgator VPS Coupon Hostgator Reseller Coupon Hostgator 1 cent coupon

  • Linkage issues mean that "helper functions" are unsuitable for use here. As the DOM implementation and each language implementation are individual components, public static functions are not globally reachable. Either xpcom interfaces or fully inline C++ classes can be used to assist with these tasks.

Implementation idea #1

Add DropScriptObect/HoldScriptObject to nsILanguageRuntime (a new class - a 'singleton' for each language - it creates nsIScriptContext objects amongst other things). Code then only needs an integer language ID to be able to free the script object.

A downside of this approach is that all memory management functions take an extra penalty. Each function involves:

  • Fetching the nsDOMScriptObjectFactory service.
  • Fetching the nsILanguageRuntime from the nsDOMScriptObjectFactory passing the int langID.
  • Calling the nsILanguageRuntime::Drop/HoldScriptObject function.

This problem could be solved by either:

  • Duplicating these functions on nsIScriptContext, so code that does have a context available can do so without the additional overhead.
  • Having performance critical code keep a reference to the nsILanguageRuntime.

Even still, a key problem to this approach remains its fragility. By explicitly requiring manual Drop/Hold calls, there is a lot of scope for programmer error in the face of error conditions (ie, early returns) to cause difficult to track leaks. The inevitability of such errors is evidenced by nsCOMPtr and various other tricks already in the code base.

Implementation idea #2

This implementation builds on #1 as its core framework, and attempts to add a "helper class" (nsScriptObjectHolder) to automate the management of these objects. All nsIScriptContext methods that return a "new" script object now take a reference to one of these objects.

The public prototype for this class is:

class nsScriptObjectHolder {
public:
   nsScriptObjectHolder();
   nsScriptObjectHolder(PRUint32 aLangID, void *aObject);
   nsScriptObjectHolder(const nsScriptObjectHolder& other);
   ~nsScriptObjectHolder();
   nsScriptObjectHolder &operator=(const nsScriptObjectHolder &other);
   PRBool operator!() const;
   operator void *() const;
   nsresult set(PRUint32 langID, void *object);
   nsresult set(void *object);
   nsresult set(const nsScriptObjectHolder &other);
   nsresult clear();
   PRUint32 mLangID;
};

the nsIScriptContext methods changed are CompileScript, CompileEventHandler, GetBoundEventHandler, CompileFunction and Deserialize, with all changes similar to:

   virtual nsresult GetBoundEventHandler(nsISupports* aTarget, void *aScope,
                                         nsIAtom* aName,
-                                        void** aHandler) = 0;
+                                        nsScriptObjectHolder &aHandler) = 0;


Code calling these methods changes from:

 void *handler = nsnull;
 rv = context->GetBoundEventHandler(..., &handler);
 if (handler)
   context->CallEventHandler(..., handler)

to:

 nsScriptObjectHolder handler;
 rv = context->GetBoundEventHandler(..., handler);
 if (handler)
   context->CallEventHandler(..., handler)

The use of "operator void *" and "operator !" means that much existing code that uses |void *| can remain unchanged once the variable has changed to nsScriptObjectHolder. For example, an nsScriptObjectHolder can be passed to nsIScriptContext::CallEventHandler, even though the prototype remains |void *|.

Problems

  • The nsScriptObjectHolder class must have a fully inline implementation. Any attempt to use methods defined in gklayout.dll mean the class can not be used by external languages (such as Python).
  • This does not lend itself to allowing DropScriptObject/HoldScriptObject on both nsIScriptContext and nsILanguageRuntime. As not all callers can use nsIScriptContext, it must be nsILanguageRuntime. This means each memory management function is slightly expensive, as detailed above.

Implementation idea #3

Use a real XPCOM interface. Something like:

interface nsIScriptObjectHolder : public nsISupports
{
   virtual void *GetScriptObject();
}

and all nsIScriptContext methods return one of these interfaces instead of a |void *|. The memory management is then implicitly tied up in the lifetime of the interface itself.

Problems

  • Fetching the |void *| language object becomes a virtual method.
  • Each call requires a new allocation for the interface.

Notes

Implementation idea #2 has been implemented and works well. However, concerns exist regarding the performance overhead in terms of speed and bloat.