Interfacing with the XPCOM cycle collector: Difference between revisions

From MozillaWiki
Jump to navigation Jump to search
No edit summary
 
(redirect to copy on MDC)
 
(One intermediate revision by one other user not shown)
Line 1: Line 1:
== XPCOM cycle collection tutorial ==
#REDIRECT [[mdc:Interfacing with the XPCOM cycle collector]]
 
This is a quick overview of the cycle collector we've now landed into XPCOM, including a description of the steps involved in modifying an existing class to participate in XPCOM cycle collection. If you have a class that you think is involved in a cyclical-ownership leak, this page is for you.
 
=== What the cycle collector does ===
 
The cycle collector spends most of its time accumulating (and forgetting about) pointers to XPCOM objects that ''might'' be involved in garbage cycles. This is the idle stage of the collector's operation, in which special variants of <tt>nsAutoRefCnt</tt> register and unregister themselves very rapidly with the collector, as they pass through a "suspicious" refcount event (from N+1 to N, for nonzero N).
 
Periodically the collector wakes up and examines any suspicious pointers that have been sitting in its buffer for a while. This is the scanning stage of the collector's operation. In this stage the collector repeatedly asks each candidate for a singleton cycle-collection helper class, and if that helper exists, the collector asks the helper to describe the candidate's (owned) children. This way the collector builds a picture of the ownership subgraph reachable from suspicious objects.
 
If the collector finds a group of objects that all refer back to one another, and establishes that the objects' reference counts are all accounted for by internal pointers within the group, it considers that group ''cyclical garbage'', which it then attempts to free. This is the unlinking stage of the collectors operation. In this stage the collector walks through the garbage objects it has found, again consulting with their helper objects, asking the helper objects to "unlink" each object from its immediate children.
 
Note that the collector also knows how to walk through the JS heap, and can locate ownership cycles that pass in and out of it.
 
=== How the collector can fail ===
 
The cycle collector is a conservative device. There are situations in which it will fail to collect a garbage cycle.
 
# It does not suspect any pointers by default; objects must ''suspect themselves'', typically by using an <tt>nsCycleCollectingAutoRefCnt</tt> rather than a <tt>nsAutoRefCnt</tt>.
# It only traverses objects that return a helper object when QI'ed to <tt>nsICycleCollectionParticipant</tt>. If it encounters an unknown edge during its traversal, it gives up on that edge; this means that every edge involved in a cycle must be participating, otherwise the cycle will not be found.
# The <tt>Traverse</tt> and <tt>Unlink</tt> methods on the helper object are not magic; they are programmer-supplied and must be correct, or else the collector will fail.
# The collector does not know how to find temporary owning pointers that exist on the stack, so it is important that it only run from near the top-loop of the program. It will not crash if there are extra owning pointers, but it will find itself unable to account for the reference counts it finds in the owned objects, so may fail to collect cycles.
 
=== How to make your classes participate ===
 
The interface between the cycle collector and your classes can be accessed directly using the contents of <tt>xpcom/base/nsCycleCollector.h</tt>, but there are convenience macros for annotating your classes in <tt>xpcom/base/nsCycleCollectionParticipant.h</tt> that are much easier to use. In general, assuming you are modifying class <tt>nsFoo</tt> with two <tt>nsCOMPtr</tt> edges <tt>mBar</tt> and <tt>mBaz</tt>, the process can be distilled to a few simple modifications:
 
# Include the header <tt>nsCycleCollectionParticipant.h</tt> in both <tt>nsFoo.h</tt> and <tt>nsFoo.cpp</tt>.
# Change the line <tt>NS_DECL_ISUPPORTS</tt> to <tt>NS_DECL_CYCLE_COLLECTING_ISUPPORTS</tt> in the definition of <tt>nsFoo</tt>.
# Add a line <tt>NS_DECL_CYCLE_COLLECTION_CLASS(nsFoo)</tt> within the public portion of definition of <tt>nsFoo</tt>.
# Add a line <tt>NS_INTERFACE_MAP_ENTRY_CYCLE_COLLECTION(nsFoo)</tt> to the interface map of <tt>nsFoo</tt> in <tt>nsFoo.cpp</tt>.
# Change the line <tt>NS_IMPL_ADDREF(nsFoo)</tt> to <tt>NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFoo)</tt> in <tt>nsFoo.cpp</tt>.
# Change the line <tt>NS_IMPL_RELEASE(nsFoo)</tt> to <tt>NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFoo)</tt> in <tt>nsFoo.cpp</tt>.
# Add a line <tt>NS_IMPL_CYCLE_COLLECTION_CLASS_2(nsFoo, mBar, mBaz)</tt> in <tt>nsFoo.cpp</tt>.
 
It is possible that your class has more complicated structure than this picture. For example, your class may have multiple <tt>nsISupports</tt> base classes, which requires the use of some <tt>*_AMBIGUOUS</tt> macros that perform a disambiguating downcast. Or your class may have a complicated ownership structure, such that the simple <tt>NS_IMPL_CYCLE_COLLECTION_CLASS_<i>N</i></tt> macros are insufficient; in this case you might need to implement the ''Traverse'' and ''Unlink'' methods of your helper class manually. It's helpful even in these cases to use the <tt>NS_IMPL_CYCLE_COLLECTION_TRAVERSE_{BEGIN,END}</tt> and <tt>NS_IMPL_CYCLE_COLLECTION_UNLINK_{BEGIN,END}</tt> macros. You can see an example of their use in some more complicated classes such as <tt>content/base/src/nsGenericElement.cpp</tt>.

Latest revision as of 20:43, 23 August 2007