[asterisk-commits] murf: trunk r114175 - in /trunk: ./ include/asterisk/ main/ utils/

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Wed Apr 16 12:45:29 CDT 2008


Author: murf
Date: Wed Apr 16 12:45:28 2008
New Revision: 114175

URL: http://svn.digium.com/view/asterisk?view=rev&rev=114175
Log:
Introducing various astobj2 enhancements, chief being a refcount tracing feature, and various documentation updates in astobj2.h, and the addition of standalone utility, refcounter, that will filter the trace output for unbalanced, unfreed objects. This comes from the team/murf/bug11210 branch.

Added:
    trunk/utils/refcounter.c   (with props)
Modified:
    trunk/CHANGES
    trunk/include/asterisk/astobj2.h
    trunk/main/astobj2.c
    trunk/utils/Makefile

Modified: trunk/CHANGES
URL: http://svn.digium.com/view/asterisk/trunk/CHANGES?view=diff&rev=114175&r1=114174&r2=114175
==============================================================================
--- trunk/CHANGES (original)
+++ trunk/CHANGES Wed Apr 16 12:45:28 2008
@@ -618,6 +618,11 @@
   * Added Doubly-linked lists after the fashion of linkedlists.h. They are in
     dlinkedlists.h. Doubly-linked lists feature fast deletion times.
     Added regression tests to the tests/ dir, also.
+  * Added a refcount trace feature to astobj2 for those trying to balance
+    object creation, deletion; work, play; space and time. See the
+    notes in astobj2.h. Also, see utils/refcounter as well, as a
+    quick way to find unbalanced refcounts in what could be a sea
+    of objects that were balanced.
   * Added logging to 'make update' command.  See update.log
   * Added strictrtp option to rtp.conf. If enabled this will drop RTP packets that
      do not come from the remote party.

Modified: trunk/include/asterisk/astobj2.h
URL: http://svn.digium.com/view/asterisk/trunk/include/asterisk/astobj2.h?view=diff&rev=114175&r1=114174&r2=114175
==============================================================================
--- trunk/include/asterisk/astobj2.h (original)
+++ trunk/include/asterisk/astobj2.h Wed Apr 16 12:45:28 2008
@@ -64,12 +64,7 @@
     ao2_ref(o, -1)
 
   causing the destructor to be called (and then memory freed) when
-  the refcount goes to 0. This is also available as ao2_unref(o),
-  and returns NULL as a convenience, so you can do things like
-
-	o = ao2_unref(o);
-
-  and clean the original pointer to prevent errors.
+  the refcount goes to 0. 
 
 - ao2_ref(o, +1) can be used to modify the refcount on the
   object in case we want to pass it around.
@@ -121,6 +116,8 @@
 The function returns NULL in case of errors (and the object
 is not inserted in the container). Other values mean success
 (we are not supposed to use the value as a pointer to anything).
+Linking an object to a container increases its refcount by 1
+automatically.
 
 \note While an object o is in a container, we expect that
 my_hash_fn(o) will always return the same value. The function
@@ -136,6 +133,245 @@
 - \ref astobj2.h All documentation for functions and data structures
 
  */
+
+/*
+\note DEBUGGING REF COUNTS BIBLE:
+An interface to help debug refcounting is provided 
+in this package. It is dependent on the REF_DEBUG macro being
+defined in a source file, before the #include of astobj2.h, 
+and in using variants of the normal ao2_xxxx functions
+that are named ao2_t_xxxx instead, with an extra argument, a string,
+that will be printed out into /tmp/refs when the refcount for an
+object is changed.
+
+  these ao2_t_xxxx variants are provided:
+
+ao2_t_alloc(arg1, arg2, arg3)
+ao2_t_ref(arg1,arg2,arg3)
+ao2_t_container_alloc(arg1,arg2,arg3,arg4)
+ao2_t_link(arg1, arg2, arg3)
+ao2_t_unlink(arg1, arg2, arg3)
+ao2_t_callback(arg1,arg2,arg3,arg4,arg5) 
+ao2_t_find(arg1,arg2,arg3,arg4)
+ao2_t_iterator_next(arg1, arg2)
+
+If you study each argument list, you will see that these functions all have
+one extra argument that their ao2_xxx counterpart. The last argument in
+each case is supposed to be a string pointer, a "tag", that should contain
+enough of an explanation, that you can pair operations that increment the 
+ref count, with operations that are meant to decrement the refcount.
+
+Each of these calls will generate at least one line of output in /tmp/refs.
+These lines look like this:
+...
+0x8756f00 =1   chan_sip.c:22240:load_module (allocate users)
+0x86e3408 =1   chan_sip.c:22241:load_module (allocate peers)
+0x86dd380 =1   chan_sip.c:22242:load_module (allocate peers_by_ip)
+0x822d020 =1   chan_sip.c:22243:load_module (allocate dialogs)
+0x8930fd8 =1   chan_sip.c:20025:build_peer (allocate a peer struct)
+0x8930fd8 +1   chan_sip.c:21467:reload_config (link peer into peer table) [@1]
+0x8930fd8 -1   chan_sip.c:2370:unref_peer (unref_peer: from reload_config) [@2]
+0x89318b0 =1   chan_sip.c:20025:build_peer (allocate a peer struct)
+0x89318b0 +1   chan_sip.c:21467:reload_config (link peer into peer table) [@1]
+0x89318b0 -1   chan_sip.c:2370:unref_peer (unref_peer: from reload_config) [@2]
+0x8930218 =1   chan_sip.c:20025:build_peer (allocate a peer struct)
+0x8930218 +1   chan_sip.c:21539:reload_config (link peer into peers table) [@1]
+0x868c040 -1   chan_sip.c:2424:dialog_unlink_all (unset the relatedpeer->call field in tandem with relatedpeer field itself) [@2]
+0x868c040 -1   chan_sip.c:2443:dialog_unlink_all (Let's unbump the count in the unlink so the poor pvt can disappear if it is time) [@1]
+0x868c040 **call destructor** chan_sip.c:2443:dialog_unlink_all (Let's unbump the count in the unlink so the poor pvt can disappear if it is time)
+0x8cc07e8 -1   chan_sip.c:2370:unref_peer (unsetting a dialog relatedpeer field in sip_destroy) [@3]
+0x8cc07e8 +1   chan_sip.c:3876:find_peer (ao2_find in peers table) [@2]
+0x8cc07e8 -1   chan_sip.c:2370:unref_peer (unref_peer, from sip_devicestate, release ref from find_peer) [@3]
+...
+
+The first column is the object address. 
+The second column reflects how the operation affected the ref count 
+    for that object. Creation sets the ref count to 1 (=1). 
+    increment or decrement and amount are specified (-1/+1).
+The remainder of the line specifies where in the file the call was made,
+    and the function name, and the tag supplied in the function call.
+
+The **call destructor** is specified when the the destroy routine is
+run for an object. It does not affect the ref count, but is important
+in debugging, because it is possible to have the astobj2 system run it
+multiple times on the same object, commonly fatal to asterisk.
+
+Sometimes you have some helper functions to do object ref/unref
+operations. Using these normally hides the place where these
+functions were called. To get the location where these functions
+were called to appear in /tmp/refs, you can do this sort of thing:
+
+#ifdef REF_DEBUG
+#define dialog_ref(arg1,arg2) dialog_ref_debug((arg1),(arg2), __FILE__, __LINE__, __PRETTY_FUNCTION__)
+#define dialog_unref(arg1,arg2) dialog_unref_debug((arg1),(arg2), __FILE__, __LINE__, __PRETTY_FUNCTION__)
+static struct sip_pvt *dialog_ref_debug(struct sip_pvt *p, char *tag, char *file, int line, const char *func)
+{
+	if (p)
+		ao2_ref_debug(p, 1, tag, file, line, func);
+	else
+		ast_log(LOG_ERROR, "Attempt to Ref a null pointer\n");
+	return p;
+}
+
+static struct sip_pvt *dialog_unref_debug(struct sip_pvt *p, char *tag, char *file, int line, const char *func)
+{
+	if (p)
+		ao2_ref_debug(p, -1, tag, file, line, func);
+	return NULL;
+}
+#else
+static struct sip_pvt *dialog_ref(struct sip_pvt *p, char *tag)
+{
+	if (p)
+		ao2_ref(p, 1);
+	else
+		ast_log(LOG_ERROR, "Attempt to Ref a null pointer\n");
+	return p;
+}
+
+static struct sip_pvt *dialog_unref(struct sip_pvt *p, char *tag)
+{
+	if (p)
+		ao2_ref(p, -1);
+	return NULL;
+}
+#endif
+
+In the above code, note that the "normal" helper funcs call ao2_ref() as
+normal, and the "helper" functions call ao2_ref_debug directly with the 
+file, function, and line number info provided. You might find this
+well worth the effort to help track these function calls in the code.
+
+To find out why objects are not destroyed (a common bug), you can 
+edit the source file to use the ao2_t_* variants, add the #define REF_DEBUG 1 
+before the #include "asterisk/astobj2.h" line, and add a descriptive 
+tag to each call. Recompile, and run Asterisk, exit asterisk with
+"stop gracefully", which should result in every object being destroyed.
+Then, you can "sort -k 1 /tmp/refs > x1" to get a sorted list of
+all the objects, or you can use "util/refcounter" to scan the file
+for you and output any problems it finds.
+
+The above may seem astronomically more work than it is worth to debug
+reference counts, which may be true in "simple" situations, but for 
+more complex situations, it is easily worth 100 times this effort to
+help find problems.
+
+To debug, pair all calls so that each call that increments the 
+refcount is paired with a corresponding call that decrements the
+count for the same reason. Hopefully, you will be left with one 
+or more unpaired calls. This is where you start your search!
+
+For instance, here is an example of this for a dialog object in 
+chan_sip, that was not getting destroyed, after I moved the lines around
+to pair operations:
+
+   0x83787a0 =1   chan_sip.c:5733:sip_alloc (allocate a dialog(pvt) struct)
+   0x83787a0 -1   chan_sip.c:19173:sip_poke_peer (unref dialog at end of sip_poke_peer, obtained from sip_alloc, just before it goes out of scope) [@4]
+
+   0x83787a0 +1   chan_sip.c:5854:sip_alloc (link pvt into dialogs table) [@1]
+   0x83787a0 -1   chan_sip.c:19150:sip_poke_peer (About to change the callid -- remove the old name) [@3]
+   0x83787a0 +1   chan_sip.c:19152:sip_poke_peer (Linking in under new name) [@2]
+   0x83787a0 -1   chan_sip.c:2399:dialog_unlink_all (unlinking dialog via ao2_unlink) [@5]
+
+   0x83787a0 +1   chan_sip.c:19130:sip_poke_peer (copy sip alloc from p to peer->call) [@2]
+
+
+   0x83787a0 +1   chan_sip.c:2996:__sip_reliable_xmit (__sip_reliable_xmit: setting pkt->owner) [@3]
+   0x83787a0 -1   chan_sip.c:2425:dialog_unlink_all (remove all current packets in this dialog, and the pointer to the dialog too as part of __sip_destroy) [@4]
+
+   0x83787a0 +1   chan_sip.c:22356:unload_module (iterate thru dialogs) [@4]
+   0x83787a0 -1   chan_sip.c:22359:unload_module (toss dialog ptr from iterator_next) [@5]
+
+
+   0x83787a0 +1   chan_sip.c:22373:unload_module (iterate thru dialogs) [@3]
+   0x83787a0 -1   chan_sip.c:22375:unload_module (throw away iterator result) [@2]
+
+   0x83787a0 +1   chan_sip.c:2397:dialog_unlink_all (Let's bump the count in the unlink so it doesn't accidentally become dead before we are done) [@4]
+   0x83787a0 -1   chan_sip.c:2436:dialog_unlink_all (Let's unbump the count in the unlink so the poor pvt can disappear if it is time) [@3]
+
+As you can see, only one unbalanced operation is in the list, a ref count increment when
+the peer->call was set, but no corresponding decrement was made...
+
+Hopefully this helps you narrow your search and find those bugs.
+
+THE ART OF REFERENCE COUNTING
+(by Steve Murphy)
+SOME TIPS for complicated code, and ref counting:
+
+1. Theoretically, passing a refcounted object pointer into a function 
+call is an act of copying the reference, and could be refcounted.
+But, upon examination, this sort of refcounting will explode the amount
+of code you have to enter, and for no tangible benefit, beyond
+creating more possible failure points/bugs. It will even 
+complicate your code and make debugging harder, slow down your program
+doing useless increments and decrements of the ref counts.
+
+2. It is better to track places where a ref counted pointer
+is copied into a structure or stored. Make sure to decrement the refcount
+of any previous pointer that might have been there, if setting
+this field might erase a previous pointer. ao2_find and iterate_next
+internally increment the ref count when they return a pointer, so 
+you need to decrement the count before the pointer goes out of scope.
+
+3. Any time you decrement a ref count, it may be possible that the
+object will be destroyed (freed) immediately by that call. If you
+are destroying a series of fields in a refcounted object, and 
+any of the unref calls might possibly result in immediate destruction,
+you can first increment the count to prevent such behavior, then 
+after the last test, decrement the pointer to allow the object
+to be destroyed, if the refcount would be zero.
+
+Example:
+
+	dialog_ref(dialog, "Let's bump the count in the unlink so it doesn't accidentally become dead before we are done");
+
+	ao2_t_unlink(dialogs, dialog, "unlinking dialog via ao2_unlink");
+
+	*//* Unlink us from the owner (channel) if we have one *//*
+	if (dialog->owner) {
+		if (lockowner)
+			ast_channel_lock(dialog->owner);
+		ast_debug(1, "Detaching from channel %s\n", dialog->owner->name);
+		dialog->owner->tech_pvt = dialog_unref(dialog->owner->tech_pvt, "resetting channel dialog ptr in unlink_all");
+		if (lockowner)
+			ast_channel_unlock(dialog->owner);
+	}
+	if (dialog->registry) {
+		if (dialog->registry->call == dialog)
+			dialog->registry->call = dialog_unref(dialog->registry->call, "nulling out the registry's call dialog field in unlink_all");
+		dialog->registry = registry_unref(dialog->registry, "delete dialog->registry");
+	}
+    ...
+ 	dialog_unref(dialog, "Let's unbump the count in the unlink so the poor pvt can disappear if it is time");
+   
+In the above code, the ao2_t_unlink could end up destroying the dialog
+object; if this happens, then the subsequent usages of the dialog
+pointer could result in a core dump. So, we 'bump' the
+count upwards before beginning, and then decrementing the count when
+we are finished. This is analogous to 'locking' or 'protecting' operations
+for a short while.
+
+4. One of the most insidious problems I've run into when converting
+code to do ref counted automatic destruction, is in the destruction
+routines. Where a "destroy" routine had previously been called to
+get rid of an object in non-refcounted code, the new regime demands
+that you tear that "destroy" routine into two pieces, one that will
+tear down the links and 'unref' them, and the other to actually free
+and reset fields. A destroy routine that does any reference deletion
+for its own object, will never be called. Another insidious problem
+occurs in mutually referenced structures. As an example, a dialog contains
+a pointer to a peer, and a peer contains a pointer to a dialog. Watch
+out that the destruction of one doesn't depend on the destruction of the
+other, as in this case a dependency loop will result in neither being
+destroyed!
+
+Given the above, you should be ready to do a good job!
+
+murf
+
+*/
+
+
 
 /*! \brief
  * Typedef for an object destructor. This is called just before freeing
@@ -160,7 +396,22 @@
  * - the returned pointer cannot be free()'d or realloc()'ed;
  *   rather, we just call ao2_ref(o, -1);
  */
-void *ao2_alloc(const size_t data_size, ao2_destructor_fn destructor_fn);
+
+#ifdef REF_DEBUG
+
+
+#define ao2_t_alloc(arg1, arg2, arg3) _ao2_alloc_debug((arg1), (arg2), (arg3),  __FILE__, __LINE__, __PRETTY_FUNCTION__)
+#define ao2_alloc(arg1, arg2)         _ao2_alloc_debug((arg1), (arg2), "",  __FILE__, __LINE__, __PRETTY_FUNCTION__)
+
+#else
+
+#define ao2_t_alloc(arg1,arg2,arg3) _ao2_alloc((arg1), (arg2))
+#define ao2_alloc(arg1,arg2)        _ao2_alloc((arg1), (arg2))
+
+#endif
+void *_ao2_alloc_debug(const size_t data_size, ao2_destructor_fn destructor_fn, char *tag, char *file, int line, const char *funcname);
+void *_ao2_alloc(const size_t data_size, ao2_destructor_fn destructor_fn);
+
 
 /*! \brief
  * Reference/unreference an object and return the old refcount.
@@ -182,12 +433,21 @@
  * can go away is when we release our reference, and it is
  * the last one in existence.
  */
-int ao2_ref(void *o, int delta);
+
+#ifdef REF_DEBUG
+#define ao2_t_ref(arg1,arg2,arg3) _ao2_ref_debug((arg1), (arg2), (arg3),  __FILE__, __LINE__, __PRETTY_FUNCTION__)
+#define ao2_ref(arg1,arg2)        _ao2_ref_debug((arg1), (arg2), "",  __FILE__, __LINE__, __PRETTY_FUNCTION__)
+#else
+#define ao2_t_ref(arg1,arg2,arg3) _ao2_ref((arg1), (arg2))
+#define ao2_ref(arg1,arg2)        _ao2_ref((arg1), (arg2))
+#endif
+int _ao2_ref_debug(void *o, int delta, char *tag, char *file, int line, const char *funcname);
+int _ao2_ref(void *o, int delta);
 
 /*! \brief
  * Lock an object.
  * 
- * \param a A pointer to the object we want lock.
+ * \param a A pointer to the object we want to lock.
  * \return 0 on success, other values on error.
  */
 int ao2_lock(void *a);
@@ -200,8 +460,28 @@
  */
 int ao2_unlock(void *a);
 
+/*! \brief
+ * Try locking-- (don't block if fail)
+ *
+ * \param a A pointer to the object we want to lock.
+ * \return 0 on success, other values on error.
+ */
+int ao2_trylock(void *a);
+
+/*! \brief
+ * Return the lock address of an object
+ *
+ * \param a A pointer to the object we want.
+ * \return the address of the lock, else NULL.
+ * 
+ * This function comes in handy mainly for debugging locking 
+ * situations, where the locking trace code reports the 
+ * lock address, this allows you to correlate against
+ * object address, to match objects to reported locks.
+ */
+void *ao2_object_get_lockaddr(void *obj);
+
 /*! 
- *
  \page AstObj2_Containers AstObj2 Containers
 
 Containers are data structures meant to store several objects,
@@ -217,17 +497,44 @@
   -  c = \b ao2_container_alloc(size, cmp_fn, hash_fn)
 	allocate a container with desired size and default compare
 	and hash function
+         -The compare function returns an int, which
+         can be 0 for not found, CMP_STOP to stop end a traversal,
+         or CMP_MATCH if they are equal
+         -The hash function returns an int. The hash function
+         takes two argument, the object pointer and a flags field,
 
   -  \b ao2_find(c, arg, flags)
 	returns zero or more element matching a given criteria
-	(specified as arg). Flags indicate how many results we
-	want (only one or all matching entries), and whether we
-	should unlink the object from the container.
+	(specified as arg). 'c' is the container pointer. Flags 
+    can be:
+	OBJ_UNLINK - to remove the object, once found, from the container.
+	OBJ_NODATA - don't return the object if found (no ref count change)
+	OBJ_MULTIPLE - don't stop at first match (not implemented)
+	OBJ_POINTER	- if set, 'arg' is an object pointer, and a hashtable
+                  search will be done. If not, a traversal is done.
 
   -  \b ao2_callback(c, flags, fn, arg)
 	apply fn(obj, arg) to all objects in the container.
 	Similar to find. fn() can tell when to stop, and
 	do anything with the object including unlinking it.
+	  - c is the container;
+      - flags can be 
+	     OBJ_UNLINK   - to remove the object, once found, from the container.
+	     OBJ_NODATA   - don't return the object if found (no ref count change)
+	     OBJ_MULTIPLE - don't stop at first match (not implemented)
+	     OBJ_POINTER  - if set, 'arg' is an object pointer, and a hashtable
+                        search will be done. If not, a traversal is done through
+                        all the hashtable 'buckets'..
+      - fn is a func that returns int, and takes 3 args: 
+        (void *obj, void *arg, int flags);
+          obj is an object
+          arg is the same as arg passed into ao2_callback
+          flags is the same as flags passed into ao2_callback
+         fn returns:
+           0: no match, keep going
+           CMP_STOP: stop search, no match
+           CMP_MATCH: This object is matched.
+
 	Note that the entire operation is run with the container
 	locked, so noone else can change its content while we work on it.
 	However, we pay this with the fact that doing
@@ -343,8 +650,19 @@
  *
  * destructor is set implicitly.
  */
-struct ao2_container *ao2_container_alloc(const uint n_buckets,
-		ao2_hash_fn *hash_fn, ao2_callback_fn *cmp_fn);
+
+#ifdef REF_DEBUG
+#define ao2_t_container_alloc(arg1,arg2,arg3,arg4) _ao2_container_alloc_debug((arg1), (arg2), (arg3), (arg4),  __FILE__, __LINE__, __PRETTY_FUNCTION__)
+#define ao2_container_alloc(arg1,arg2,arg3)        _ao2_container_alloc_debug((arg1), (arg2), (arg3), "",  __FILE__, __LINE__, __PRETTY_FUNCTION__)
+#else
+#define ao2_t_container_alloc(arg1,arg2,arg3,arg4) _ao2_container_alloc((arg1), (arg2), (arg3))
+#define ao2_container_alloc(arg1,arg2,arg3)        _ao2_container_alloc((arg1), (arg2), (arg3))
+#endif
+struct ao2_container *_ao2_container_alloc(const uint n_buckets,
+										  ao2_hash_fn *hash_fn, ao2_callback_fn *cmp_fn);
+struct ao2_container *_ao2_container_alloc_debug(const uint n_buckets,
+												ao2_hash_fn *hash_fn, ao2_callback_fn *cmp_fn, 
+												char *tag, char *file, int line, const char *funcname);
 
 /*! \brief
  * Returns the number of elements in a container.
@@ -376,7 +694,16 @@
  * \note This function automatically increases the reference count to account
  *       for the reference that the container now holds to the object.
  */
-void *ao2_link(struct ao2_container *c, void *newobj);
+#ifdef REF_DEBUG
+
+#define ao2_t_link(arg1, arg2, arg3) _ao2_link_debug((arg1), (arg2), (arg3),  __FILE__, __LINE__, __PRETTY_FUNCTION__)
+#define ao2_link(arg1, arg2)         _ao2_link_debug((arg1), (arg2), "",  __FILE__, __LINE__, __PRETTY_FUNCTION__)
+#else
+#define ao2_t_link(arg1, arg2, arg3) _ao2_link((arg1), (arg2))
+#define ao2_link(arg1, arg2)         _ao2_link((arg1), (arg2))
+#endif
+void *_ao2_link_debug(struct ao2_container *c, void *new_obj, char *tag, char *file, int line, const char *funcname);
+void *_ao2_link(struct ao2_container *c, void *newobj);
 
 /*!
  * \brief Remove an object from the container
@@ -391,9 +718,19 @@
  *       be called.
  *
  * \note If the object gets unlinked from the container, the container's
- *       reference to the object will be automatically released.  
- */
-void *ao2_unlink(struct ao2_container *c, void *obj);
+ *       reference to the object will be automatically released. (The 
+ *       refcount will be decremented).
+ */
+#ifdef REF_DEBUG
+#define ao2_t_unlink(arg1, arg2, arg3) _ao2_unlink_debug((arg1), (arg2), (arg3),  __FILE__, __LINE__, __PRETTY_FUNCTION__)
+#define ao2_unlink(arg1, arg2)         _ao2_unlink_debug((arg1), (arg2), "",  __FILE__, __LINE__, __PRETTY_FUNCTION__)
+#else
+#define ao2_t_unlink(arg1, arg2, arg3) _ao2_unlink((arg1), (arg2))
+#define ao2_unlink(arg1, arg2)         _ao2_unlink((arg1), (arg2))
+#endif
+void *_ao2_unlink_debug(struct ao2_container *c, void *obj, char *tag, char *file, int line, const char *funcname);
+void *_ao2_unlink(struct ao2_container *c, void *obj);
+
 
 /*! \brief Used as return value if the flag OBJ_MULTIPLE is set */
 struct ao2_list {
@@ -407,10 +744,30 @@
  * in a container, as described below.
  * 
  * \param c A pointer to the container to operate on.
- * \param arg passed to the callback.
  * \param flags A set of flags specifying the operation to perform,
 	partially used by the container code, but also passed to
 	the callback.
+     - If OBJ_NODATA is set, ao2_callback will return NULL. No refcounts
+       of any of the traversed objects will be incremented.
+       On the converse, if it is NOT set (the default), The ref count
+       of each object for which CMP_MATCH was set will be incremented,
+       and you will have no way of knowing which those are, until
+       the multiple-object-return functionality is implemented.
+     - If OBJ_POINTER is set, the traversed items will be restricted
+       to the objects in the bucket that the object key hashes to.
+ * \param cb_fn A function pointer, that will be called on all
+    objects, to see if they match. This function returns CMP_MATCH
+    if the object is matches the criteria; CMP_STOP if the traversal
+    should immediately stop, or both (via bitwise ORing), if you find a 
+    match and want to end the traversal, and 0 if the object is not a match, 
+    but the traversal should continue. This is the function that is applied
+    to each object traversed. It's arguments are:
+        (void *obj, void *arg, int flags), where:
+          obj is an object
+          arg is the same as arg passed into ao2_callback
+          flags is the same as flags passed into ao2_callback (flags are
+           also used by ao2_callback).
+ * \param arg passed to the callback.
  * \return 	A pointer to the object found/marked, 
  * 		a pointer to a list of objects matching comparison function,
  * 		NULL if not found.
@@ -459,14 +816,32 @@
  * \note When the returned object is no longer in use, ao2_ref() should
  * be used to free the additional reference possibly created by this function.
  */
-void *ao2_callback(struct ao2_container *c,
-	enum search_flags flags,
-	ao2_callback_fn *cb_fn, void *arg);
+#ifdef REF_DEBUG
+#define ao2_t_callback(arg1,arg2,arg3,arg4,arg5) _ao2_callback_debug((arg1), (arg2), (arg3), (arg4), (arg5),  __FILE__, __LINE__, __PRETTY_FUNCTION__)
+#define ao2_callback(arg1,arg2,arg3,arg4)        _ao2_callback_debug((arg1), (arg2), (arg3), (arg4), "",  __FILE__, __LINE__, __PRETTY_FUNCTION__)
+#else
+#define ao2_t_callback(arg1,arg2,arg3,arg4,arg5) _ao2_callback((arg1), (arg2), (arg3), (arg4))
+#define ao2_callback(arg1,arg2,arg3,arg4)        _ao2_callback((arg1), (arg2), (arg3), (arg4))
+#endif
+void *_ao2_callback_debug(struct ao2_container *c, enum search_flags flags,
+						 ao2_callback_fn *cb_fn, void *arg, char *tag, 
+						 char *file, int line, const char *funcname);
+void *_ao2_callback(struct ao2_container *c,
+				   enum search_flags flags,
+				   ao2_callback_fn *cb_fn, void *arg);
 
 /*! ao2_find() is a short hand for ao2_callback(c, flags, c->cmp_fn, arg)
  * XXX possibly change order of arguments ?
  */
-void *ao2_find(struct ao2_container *c, void *arg, enum search_flags flags);
+#ifdef REF_DEBUG
+#define ao2_t_find(arg1,arg2,arg3,arg4) _ao2_find_debug((arg1), (arg2), (arg3), (arg4),  __FILE__, __LINE__, __PRETTY_FUNCTION__)
+#define ao2_find(arg1,arg2,arg3)        _ao2_find_debug((arg1), (arg2), (arg3), "",  __FILE__, __LINE__, __PRETTY_FUNCTION__)
+#else
+#define ao2_t_find(arg1,arg2,arg3,arg4) _ao2_find((arg1), (arg2), (arg3))
+#define ao2_find(arg1,arg2,arg3)        _ao2_find((arg1), (arg2), (arg3))
+#endif
+void *_ao2_find_debug(struct ao2_container *c, void *arg, enum search_flags flags, char *tag, char *file, int line, const char *funcname);
+void *_ao2_find(struct ao2_container *c, void *arg, enum search_flags flags);
 
 /*! \brief
  *
@@ -559,9 +934,20 @@
 	uint version;
 };
 
+/* the flags field can contain F_AO2I_DONTLOCK, which will prevent 
+   ao2_iterator_next calls from locking the container while it
+   searches for the next pointer */
+
 struct ao2_iterator ao2_iterator_init(struct ao2_container *c, int flags);
-
-void *ao2_iterator_next(struct ao2_iterator *a);
+#ifdef REF_DEBUG
+#define ao2_t_iterator_next(arg1, arg2) _ao2_iterator_next_debug((arg1), (arg2),  __FILE__, __LINE__, __PRETTY_FUNCTION__)
+#define ao2_iterator_next(arg1)         _ao2_iterator_next_debug((arg1), "",  __FILE__, __LINE__, __PRETTY_FUNCTION__)
+#else
+#define ao2_t_iterator_next(arg1, arg2) _ao2_iterator_next((arg1))
+#define ao2_iterator_next(arg1)         _ao2_iterator_next((arg1))
+#endif
+void *_ao2_iterator_next_debug(struct ao2_iterator *a, char *tag, char *file, int line, const char *funcname);
+void *_ao2_iterator_next(struct ao2_iterator *a);
 
 /* extra functions */
 void ao2_bt(void);	/* backtrace */

Modified: trunk/main/astobj2.c
URL: http://svn.digium.com/view/asterisk/trunk/main/astobj2.c?view=diff&rev=114175&r1=114174&r2=114175
==============================================================================
--- trunk/main/astobj2.c (original)
+++ trunk/main/astobj2.c Wed Apr 16 12:45:28 2008
@@ -25,6 +25,7 @@
 #include "asterisk/astobj2.h"
 #include "asterisk/utils.h"
 #include "asterisk/cli.h"
+#define REF_FILE "/tmp/refs"
 
 /*!
  * astobj2 objects are always preceded by this data structure,
@@ -126,6 +127,18 @@
  */
 #define EXTERNAL_OBJ(_p)	((_p) == NULL ? NULL : (_p)->user_data)
 
+/* the underlying functions common to debug and non-debug versions */
+
+static int __ao2_ref(void *user_data, const int delta);
+static void *__ao2_alloc(size_t data_size, ao2_destructor_fn destructor_fn);
+static struct ao2_container *__ao2_container_alloc(struct ao2_container *c, const uint n_buckets, ao2_hash_fn *hash_fn,
+											ao2_callback_fn *cmp_fn);
+static struct bucket_list *__ao2_link(struct ao2_container *c, void *user_data);
+static void *__ao2_callback(struct ao2_container *c,
+	const enum search_flags flags, ao2_callback_fn *cb_fn, void *arg,
+					 char *tag, char *file, int line, const char *funcname);
+static void * __ao2_iterator_next(struct ao2_iterator *a, struct bucket_list **q);
+
 int ao2_lock(void *user_data)
 {
 	struct astobj2 *p = INTERNAL_OBJ(user_data);
@@ -154,17 +167,71 @@
 	return ast_mutex_unlock(&p->priv_data.lock);
 }
 
+int ao2_trylock(void *user_data)
+{
+	struct astobj2 *p = INTERNAL_OBJ(user_data);
+	int ret;
+	
+	if (p == NULL)
+		return -1;
+	ret =  ast_mutex_trylock(&p->priv_data.lock);
+#ifdef AO2_DEBUG
+	if (!ret)
+		ast_atomic_fetchadd_int(&ao2.total_locked, 1);
+#endif
+	return ret;
+}
+
+void *ao2_object_get_lockaddr(void *obj)
+{
+	struct astobj2 *p = INTERNAL_OBJ(obj);
+	
+	if (p == NULL)
+		return NULL;
+
+	return &p->priv_data.lock;
+}
+
 /*
  * The argument is a pointer to the user portion.
  */
-int ao2_ref(void *user_data, const int delta)
-{
+
+
+int _ao2_ref_debug(void *user_data, const int delta, char *tag, char *file, int line, const char *funcname)
+{
+	struct astobj2 *obj = INTERNAL_OBJ(user_data);
+	
+	if (obj == NULL)
+		return -1;
+
+	if (delta != 0) {
+		FILE *refo = fopen(REF_FILE,"a");
+		fprintf(refo, "%p %s%d   %s:%d:%s (%s) [@%d]\n", user_data, (delta<0? "":"+"), delta, file, line, funcname, tag, obj->priv_data.ref_counter);
+		fclose(refo);
+	}
+	if (obj->priv_data.ref_counter + delta == 0 && obj->priv_data.destructor_fn != NULL) { /* this isn't protected with lock; just for o/p */
+			FILE *refo = fopen(REF_FILE,"a"); 	 
+			fprintf(refo, "%p **call destructor** %s:%d:%s (%s)\n", user_data, file, line, funcname, tag); 	 
+			fclose(refo);
+	}
+	return __ao2_ref(user_data, delta);
+}
+
+int _ao2_ref(void *user_data, const int delta)
+{
+	struct astobj2 *obj = INTERNAL_OBJ(user_data);
+
+	if (obj == NULL)
+		return -1;
+
+	return __ao2_ref(user_data, delta);
+}
+
+static int __ao2_ref(void *user_data, const int delta)
+{
+	struct astobj2 *obj = INTERNAL_OBJ(user_data);
 	int current_value;
 	int ret;
-	struct astobj2 *obj = INTERNAL_OBJ(user_data);
-
-	if (obj == NULL)
-		return -1;
 
 	/* if delta is 0, just return the refcount */
 	if (delta == 0)
@@ -183,8 +250,9 @@
 		ast_log(LOG_ERROR, "refcount %d on object %p\n", current_value, user_data);
 
 	if (current_value <= 0) { /* last reference, destroy the object */
-		if (obj->priv_data.destructor_fn != NULL) 
+		if (obj->priv_data.destructor_fn != NULL) {
 			obj->priv_data.destructor_fn(user_data);
+		}
 
 		ast_mutex_destroy(&obj->priv_data.lock);
 #ifdef AO2_DEBUG
@@ -205,7 +273,7 @@
  * We always alloc at least the size of a void *,
  * for debugging purposes.
  */
-void *ao2_alloc(size_t data_size, ao2_destructor_fn destructor_fn)
+static void *__ao2_alloc(size_t data_size, ao2_destructor_fn destructor_fn)
 {
 	/* allocation */
 	struct astobj2 *obj;
@@ -234,8 +302,37 @@
 	return EXTERNAL_OBJ(obj);
 }
 
+void *_ao2_alloc_debug(size_t data_size, ao2_destructor_fn destructor_fn, char *tag, char *file, int line, const char *funcname)
+{
+	/* allocation */
+	void *obj;
+	FILE *refo = fopen(REF_FILE,"a");
+
+	obj = __ao2_alloc(data_size, destructor_fn);
+
+	if (obj == NULL)
+		return NULL;
+	
+	if (refo) {
+		fprintf(refo, "%p =1   %s:%d:%s (%s)\n", obj, file, line, funcname, tag);
+		fclose(refo);
+	}
+
+	/* return a pointer to the user data */
+	return obj;
+}
+
+void *_ao2_alloc(size_t data_size, ao2_destructor_fn destructor_fn)
+{
+	return __ao2_alloc(data_size, destructor_fn);
+}
+
+
 /* internal callback to destroy a container. */
 static void container_destruct(void *c);
+
+/* internal callback to destroy a container. */
+static void container_destruct_debug(void *c);
 
 /* each bucket in the container is a tailq. */
 AST_LIST_HEAD_NOLOCK(bucket, bucket_list);
@@ -291,15 +388,11 @@
 /*
  * A container is just an object, after all!
  */
-struct ao2_container *
-ao2_container_alloc(const uint n_buckets, ao2_hash_fn *hash_fn,
-		ao2_callback_fn *cmp_fn)
+static struct ao2_container *__ao2_container_alloc(struct ao2_container *c, const uint n_buckets, ao2_hash_fn *hash_fn,
+											ao2_callback_fn *cmp_fn)
 {
 	/* XXX maybe consistency check on arguments ? */
 	/* compute the container size */
-	size_t container_size = sizeof(struct ao2_container) + n_buckets * sizeof(struct bucket);
-
-	struct ao2_container *c = ao2_alloc(container_size, container_destruct);
 
 	if (!c)
 		return NULL;
@@ -314,6 +407,30 @@
 #endif
 
 	return c;
+}
+
+struct ao2_container *_ao2_container_alloc_debug(const uint n_buckets, ao2_hash_fn *hash_fn,
+		ao2_callback_fn *cmp_fn, char *tag, char *file, int line, const char *funcname)
+{
+	/* XXX maybe consistency check on arguments ? */
+	/* compute the container size */
+	size_t container_size = sizeof(struct ao2_container) + n_buckets * sizeof(struct bucket);
+	struct ao2_container *c = _ao2_alloc_debug(container_size, container_destruct_debug, tag, file, line, funcname);
+
+	return __ao2_container_alloc(c, n_buckets, hash_fn, cmp_fn);
+}
+
+struct ao2_container *
+_ao2_container_alloc(const uint n_buckets, ao2_hash_fn *hash_fn,
+		ao2_callback_fn *cmp_fn)
+{
+	/* XXX maybe consistency check on arguments ? */
+	/* compute the container size */
+
+	size_t container_size = sizeof(struct ao2_container) + n_buckets * sizeof(struct bucket);
+	struct ao2_container *c = _ao2_alloc(container_size, container_destruct);
+
+	return __ao2_container_alloc(c, n_buckets, hash_fn, cmp_fn);
 }
 
 /*!
@@ -338,7 +455,8 @@
 /*
  * link an object to a container
  */
-void *ao2_link(struct ao2_container *c, void *user_data)
+
+static struct bucket_list *__ao2_link(struct ao2_container *c, void *user_data)
 {
 	int i;
 	/* create a new list entry */
@@ -363,9 +481,30 @@
 	p->version = ast_atomic_fetchadd_int(&c->version, 1);
 	AST_LIST_INSERT_TAIL(&c->buckets[i], p, entry);
 	ast_atomic_fetchadd_int(&c->elements, 1);
-	ao2_ref(user_data, +1);
-	ao2_unlock(c);
-	
+
+	/* the last two operations (ao2_ref, ao2_unlock) must be done by the calling func */
+	return p;
+}
+
+void *_ao2_link_debug(struct ao2_container *c, void *user_data, char *tag, char *file, int line, const char *funcname)
+{
+	struct bucket_list *p = __ao2_link(c, user_data);
+	
+	if (p) {
+		_ao2_ref_debug(user_data, +1, tag, file, line, funcname);
+		ao2_unlock(c);
+	}
+	return p;
+}
+
+void *_ao2_link(struct ao2_container *c, void *user_data)
+{
+	struct bucket_list *p = __ao2_link(c, user_data);
+	
+	if (p) {
+		_ao2_ref(user_data, +1);
+		ao2_unlock(c);
+	}
 	return p;
 }
 
@@ -381,12 +520,23 @@
  * Unlink an object from the container
  * and destroy the associated * ao2_bucket_list structure.
  */
-void *ao2_unlink(struct ao2_container *c, void *user_data)
+void *_ao2_unlink_debug(struct ao2_container *c, void *user_data, char *tag,
+					   char *file, int line, const char *funcname)
 {
 	if (INTERNAL_OBJ(user_data) == NULL)	/* safety check on the argument */
 		return NULL;
 
-	ao2_callback(c, OBJ_UNLINK | OBJ_POINTER | OBJ_NODATA, ao2_match_by_addr, user_data);
+	_ao2_callback_debug(c, OBJ_UNLINK | OBJ_POINTER | OBJ_NODATA, ao2_match_by_addr, user_data, tag, file, line, funcname);
+
+	return NULL;
+}
+
+void *_ao2_unlink(struct ao2_container *c, void *user_data)
+{
+	if (INTERNAL_OBJ(user_data) == NULL)	/* safety check on the argument */
+		return NULL;
+
+	_ao2_callback(c, OBJ_UNLINK | OBJ_POINTER | OBJ_NODATA, ao2_match_by_addr, user_data);
 
 	return NULL;
 }
@@ -396,6 +546,7 @@
  */ 
 static int cb_true(void *user_data, void *arg, int flags)
 {
+	ast_log(LOG_ERROR,"If you see this, something is strange!\n");
 	return CMP_MATCH;
 }
 
@@ -403,10 +554,13 @@
  * Browse the container using different stategies accoding the flags.
  * \return Is a pointer to an object or to a list of object if OBJ_MULTIPLE is 
  * specified.
- */
-void *ao2_callback(struct ao2_container *c,
-	const enum search_flags flags,
-	ao2_callback_fn *cb_fn, void *arg)
+ * Luckily, for debug purposes, the added args (tag, file, line, funcname)
+ * aren't an excessive load to the system, as the callback should not be
+ * called as often as, say, the ao2_ref func is called.
+ */
+static void *__ao2_callback(struct ao2_container *c,
+	const enum search_flags flags, ao2_callback_fn *cb_fn, void *arg,
+	char *tag, char *file, int line, const char *funcname)
 {
 	int i, last;	/* search boundaries */
 	void *ret = NULL;
@@ -461,7 +615,10 @@
 			if (!(flags & OBJ_NODATA)) {	/* if must return the object, record the value */
 				/* it is important to handle this case before the unlink */
 				ret = EXTERNAL_OBJ(cur->astobj);
-				ao2_ref(ret, 1);
+				if (tag)
+					_ao2_ref_debug(ret, 1, tag, file, line, funcname);
+				else
+					_ao2_ref(ret, 1);
 			}
 
 			if (flags & OBJ_UNLINK) {	/* must unlink */
@@ -472,7 +629,10 @@
 				AST_LIST_REMOVE_CURRENT(entry);
 				/* update number of elements and version */
 				ast_atomic_fetchadd_int(&c->elements, -1);
-				ao2_ref(EXTERNAL_OBJ(x->astobj), -1);
+				if (tag)
+					_ao2_ref_debug(EXTERNAL_OBJ(x->astobj), -1, tag, file, line, funcname);
+				else
+					_ao2_ref(EXTERNAL_OBJ(x->astobj), -1);
 				free(x);	/* free the link record */
 			}
 
@@ -496,12 +656,31 @@
 	return ret;
 }
 
+void *_ao2_callback_debug(struct ao2_container *c,
+						 const enum search_flags flags,
+						 ao2_callback_fn *cb_fn, void *arg, 
+						 char *tag, char *file, int line, const char *funcname)
+{
+	return __ao2_callback(c,flags, cb_fn, arg, tag, file, line, funcname);
+}
+
+void *_ao2_callback(struct ao2_container *c,const enum search_flags flags,
+                    ao2_callback_fn *cb_fn, void *arg)
+{
+	return __ao2_callback(c,flags, cb_fn, arg, NULL, NULL, 0, NULL);
+}
+
 /*!
  * the find function just invokes the default callback with some reasonable flags.
  */
-void *ao2_find(struct ao2_container *c, void *arg, enum search_flags flags)
-{
-	return ao2_callback(c, flags, c->cmp_fn, arg);
+void *_ao2_find_debug(struct ao2_container *c, void *arg, enum search_flags flags, char *tag, char *file, int line, const char *funcname)
+{
+	return _ao2_callback_debug(c, flags, c->cmp_fn, arg, tag, file, line, funcname);
+}
+
+void *_ao2_find(struct ao2_container *c, void *arg, enum search_flags flags)
+{
+	return _ao2_callback(c, flags, c->cmp_fn, arg);
 }
 
 /*!
@@ -520,12 +699,14 @@
 /*
  * move to the next element in the container.
  */
-void * ao2_iterator_next(struct ao2_iterator *a)
+static void * __ao2_iterator_next(struct ao2_iterator *a, struct bucket_list **q)
 {
 	int lim;
 	struct bucket_list *p = NULL;
 	void *ret = NULL;
 
+	*q = NULL;
+	
 	if (INTERNAL_OBJ(a->c) == NULL)
 		return NULL;
 
@@ -567,7 +748,22 @@
 		a->c_version = a->c->version;
 		ret = EXTERNAL_OBJ(p->astobj);
 		/* inc refcount of returned object */
-		ao2_ref(ret, 1);
+		*q = p;
+	}
+
+	return ret;
+}
+
+void * _ao2_iterator_next_debug(struct ao2_iterator *a, char *tag, char *file, int line, const char *funcname)
+{
+	struct bucket_list *p;
+	void *ret = NULL;
+
+	ret = __ao2_iterator_next(a, &p);
+	
+	if (p) {
+		/* inc refcount of returned object */
+		_ao2_ref_debug(ret, 1, tag, file, line, funcname);
 	}
 
 	if (!(a->flags & F_AO2I_DONTLOCK))
@@ -576,20 +772,55 @@
 	return ret;
 }
 
+void * _ao2_iterator_next(struct ao2_iterator *a)
+{
+	struct bucket_list *p = NULL;
+	void *ret = NULL;
+
+	ret = __ao2_iterator_next(a, &p);
+	
+	if (p) {
+		/* inc refcount of returned object */
+		_ao2_ref(ret, 1);
+	}

[... 428 lines stripped ...]



More information about the asterisk-commits mailing list