aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGES5
-rw-r--r--include/asterisk/astobj2.h436
-rw-r--r--main/astobj2.c308
-rw-r--r--utils/Makefile8
-rw-r--r--utils/refcounter.c268
5 files changed, 959 insertions, 66 deletions
diff --git a/CHANGES b/CHANGES
index 1558c5ca0..52f96c237 100644
--- a/CHANGES
+++ b/CHANGES
@@ -618,6 +618,11 @@ Miscellaneous
* 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.
diff --git a/include/asterisk/astobj2.h b/include/asterisk/astobj2.h
index b02b6cba8..5f9776004 100644
--- a/include/asterisk/astobj2.h
+++ b/include/asterisk/astobj2.h
@@ -64,12 +64,7 @@ On return from ao2_alloc():
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 @@ Once done, we can link an object to a container with
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
@@ -137,6 +134,245 @@ list. However there is no ordering among elements.
*/
+/*
+\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
* the memory for the object. It is passed a pointer to the user-defined
@@ -160,7 +396,22 @@ typedef void (*ao2_destructor_fn)(void *);
* - 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 @@ void *ao2_alloc(const size_t data_size, ao2_destructor_fn destructor_fn);
* 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_lock(void *a);
*/
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 @@ Operations on container include:
- 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 @@ struct ao2_container;
*
* 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 @@ int ao2_container_count(struct ao2_container *c);
* \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 @@ void *ao2_link(struct ao2_container *c, void *newobj);
* be called.
*
* \note If the object gets unlinked from the container, the container's
- * reference to the object will be automatically released.
+ * reference to the object will be automatically released. (The
+ * refcount will be decremented).
*/
-void *ao2_unlink(struct ao2_container *c, void *obj);
+#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 @@ struct ao2_list {
* 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 @@ struct ao2_list {
* \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 @@ struct ao2_iterator {
uint version;
};
-struct ao2_iterator ao2_iterator_init(struct ao2_container *c, int flags);
+/* 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 */
-void *ao2_iterator_next(struct ao2_iterator *a);
+struct ao2_iterator ao2_iterator_init(struct ao2_container *c, int flags);
+#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 */
diff --git a/main/astobj2.c b/main/astobj2.c
index 8c783955b..007f12545 100644
--- a/main/astobj2.c
+++ b/main/astobj2.c
@@ -25,6 +25,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#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 @@ static inline struct astobj2 *INTERNAL_OBJ(void *user_data)
*/
#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,18 +167,72 @@ int ao2_unlock(void *user_data)
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)
{
- int current_value;
- int ret;
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;
+
/* if delta is 0, just return the refcount */
if (delta == 0)
return (obj->priv_data.ref_counter);
@@ -183,8 +250,9 @@ int ao2_ref(void *user_data, const int delta)
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 @@ int ao2_ref(void *user_data, const int delta)
* 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,9 +302,38 @@ void *ao2_alloc(size_t data_size, ao2_destructor_fn destructor_fn)
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 @@ static int hash_zero(const void *user_obj, const int flags)
/*
* 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;
@@ -316,6 +409,30 @@ ao2_container_alloc(const uint n_buckets, ao2_hash_fn *hash_fn,
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);
+}
+
/*!
* return the number of elements in the container
*/
@@ -338,7 +455,8 @@ struct bucket_list {
/*
* 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 @@ void *ao2_link(struct ao2_container *c, void *user_data)
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 @@ int ao2_match_by_addr(void *user_data, void *arg, int flags)
* 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 @@ void *ao2_unlink(struct ao2_container *c, void *user_data)
*/
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 @@ static int cb_true(void *user_data, void *arg, int flags)
* 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.
+ * 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.
*/
-void *ao2_callback(struct ao2_container *c,
- const enum search_flags flags,
- ao2_callback_fn *cb_fn, void *arg)
+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 @@ void *ao2_callback(struct ao2_container *c,
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 @@ void *ao2_callback(struct ao2_container *c,
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 @@ void *ao2_callback(struct ao2_container *c,
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)
+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(c, flags, c->cmp_fn, arg);
+ 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 @@ struct ao2_iterator ao2_iterator_init(struct ao2_container *c, int flags)
/*
* 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,40 @@ found:
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))
+ ao2_unlock(a->c);
+
+ 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);
}
if (!(a->flags & F_AO2I_DONTLOCK))
@@ -581,7 +795,13 @@ found:
*/
static int cd_cb(void *obj, void *arg, int flag)
{
- ao2_ref(obj, -1);
+ _ao2_ref(obj, -1);
+ return 0;
+}
+
+static int cd_cb_debug(void *obj, void *arg, int flag)
+{
+ _ao2_ref_debug(obj, -1, "deref object via container destroy", __FILE__, __LINE__, __PRETTY_FUNCTION__);
return 0;
}
@@ -589,7 +809,18 @@ static void container_destruct(void *_c)
{
struct ao2_container *c = _c;
- ao2_callback(c, OBJ_UNLINK, cd_cb, NULL);
+ _ao2_callback(c, OBJ_UNLINK, cd_cb, NULL);
+
+#ifdef AO2_DEBUG
+ ast_atomic_fetchadd_int(&ao2.total_containers, -1);
+#endif
+}
+
+static void container_destruct_debug(void *_c)
+{
+ struct ao2_container *c = _c;
+
+ _ao2_callback_debug(c, OBJ_UNLINK, cd_cb_debug, NULL, "container_destruct_debug called", __FILE__, __LINE__, __PRETTY_FUNCTION__);
#ifdef AO2_DEBUG
ast_atomic_fetchadd_int(&ao2.total_containers, -1);
@@ -666,7 +897,7 @@ static char *handle_astobj2_test(struct ast_cli_entry *e, int cmd, struct ast_cl
* allocate a container with no default callback, and no hash function.
* No hash means everything goes in the same bucket.
*/
- c1 = ao2_container_alloc(100, NULL /* no callback */, NULL /* no hash */);
+ c1 = ao2_t_container_alloc(100, NULL /* no callback */, NULL /* no hash */,"test");
ast_cli(a->fd, "container allocated as %p\n", c1);
/*
@@ -676,42 +907,41 @@ static char *handle_astobj2_test(struct ast_cli_entry *e, int cmd, struct ast_cl
*/
for (i = 0; i < lim; i++) {
ast_mark(prof_id, 1 /* start */);
- obj = ao2_alloc(80, NULL);
+ obj = ao2_t_alloc(80, NULL,"test");
ast_mark(prof_id, 0 /* stop */);
ast_cli(a->fd, "object %d allocated as %p\n", i, obj);
sprintf(obj, "-- this is obj %d --", i);
ao2_link(c1, obj);
}
ast_cli(a->fd, "testing callbacks\n");
- ao2_callback(c1, 0, print_cb, &a->fd);
-
+ ao2_t_callback(c1, 0, print_cb, &a->fd,"test callback");
ast_cli(a->fd, "testing iterators, remove every second object\n");
{
struct ao2_iterator ai;
int x = 0;
ai = ao2_iterator_init(c1, 0);
- while ( (obj = ao2_iterator_next(&ai)) ) {
+ while ( (obj = ao2_t_iterator_next(&ai,"test")) ) {
ast_cli(a->fd, "iterator on <%s>\n", obj);
if (x++ & 1)
- ao2_unlink(c1, obj);
- ao2_ref(obj, -1);
+ ao2_t_unlink(c1, obj,"test");
+ ao2_t_ref(obj, -1,"test");
}
ast_cli(a->fd, "testing iterators again\n");
ai = ao2_iterator_init(c1, 0);
- while ( (obj = ao2_iterator_next(&ai)) ) {
+ while ( (obj = ao2_t_iterator_next(&ai,"test")) ) {
ast_cli(a->fd, "iterator on <%s>\n", obj);
- ao2_ref(obj, -1);
+ ao2_t_ref(obj, -1,"test");
}
}
ast_cli(a->fd, "testing callbacks again\n");
- ao2_callback(c1, 0, print_cb, &a->fd);
+ ao2_t_callback(c1, 0, print_cb, &a->fd,"test callback");
ast_verbose("now you should see an error message:\n");
- ao2_ref(&i, -1); /* i is not a valid object so we print an error here */
+ ao2_t_ref(&i, -1, ""); /* i is not a valid object so we print an error here */
ast_cli(a->fd, "destroy container\n");
- ao2_ref(c1, -1); /* destroy container */
+ ao2_t_ref(c1, -1, ""); /* destroy container */
handle_astobj2_stats(e, CLI_HANDLER, &fake_args);
return CLI_SUCCESS;
}
diff --git a/utils/Makefile b/utils/Makefile
index 9576cb5b0..d84907710 100644
--- a/utils/Makefile
+++ b/utils/Makefile
@@ -17,7 +17,7 @@ ASTTOPDIR?=..
.PHONY: clean all uninstall
# to get check_expr, add it to the ALL_UTILS list
-ALL_UTILS:=astman smsq stereorize streamplayer aelparse muted check_expr conf2ael hashtest2 hashtest astcanary
+ALL_UTILS:=astman smsq stereorize streamplayer aelparse muted check_expr conf2ael hashtest2 hashtest astcanary refcounter
UTILS:=$(ALL_UTILS)
LIBS += $(BKTR_LIB) # astobj2 with devmode uses backtrace
@@ -76,7 +76,7 @@ clean:
rm -f *.s *.i
rm -f md5.c strcompat.c ast_expr2.c ast_expr2f.c pbx_ael.c pval.c hashtab.c
rm -f aelparse.c aelbison.c conf2ael
- rm -f utils.c threadstorage.c sha1.c astobj2.c hashtest2 hashtest
+ rm -f utils.c threadstorage.c sha1.c astobj2.c hashtest2 hashtest refcounter
md5.c: $(ASTTOPDIR)/main/md5.c
@cp $< $@
@@ -153,6 +153,10 @@ hashtest: hashtest.o md5.o hashtab.o utils.o sha1.o strcompat.o threadstorage.o
hashtest.o: ASTCFLAGS+=-O0
+refcounter: refcounter.o md5.o hashtab.o utils.o sha1.o strcompat.o threadstorage.o clicompat.o
+
+refcounter.o: ASTCFLAGS+=-O0
+
extconf.o: extconf.c
conf2ael: conf2ael.o ast_expr2f.o ast_expr2.o hashtab.o aelbison.o aelparse.o pbx_ael.o pval.o extconf.o strcompat.o
diff --git a/utils/refcounter.c b/utils/refcounter.c
new file mode 100644
index 000000000..4c3e96e3b
--- /dev/null
+++ b/utils/refcounter.c
@@ -0,0 +1,268 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2008, Steve Murphy
+ *
+ * Steve Murphy <murf@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+/*! \file
+ *
+ * \brief A program to read in the /tmp/refs file generated
+ * by astobj2 code when the REF_DEBUG macro is defined.
+ * It will read in the file line by line, and
+ * sort the data out by object, and check to see
+ * if the refcounts balance to zero, and the object
+ * was destroyed just once. Any problems that are
+ * found are reported to stdout and the objects
+ * ref count history is printed out. If all is well,
+ * this program reads in the /tmp/refs file and
+ * generates no output. No news is good news.
+ * The contents of the /tmp/refs file looks like this:
+ *
+0x84fd718 -1 astobj2.c:926:cd_cb_debug (deref object via container destroy) [@1]
+0x84fd718 =1 chan_sip.c:19760:build_user (allocate a user struct)
+0x84fd718 +1 chan_sip.c:21558:reload_config (link user into users table) [@1]
+0x84fd718 -1 chan_sip.c:2376:unref_user (Unref the result of build_user. Now, the table link is the only one left.) [@2]
+0x84fd718 **call destructor** astobj2.c:926:cd_cb_debug (deref object via container destroy)
+ *
+ *
+ * \author Steve Murphy <murf@digium.com>
+ */
+
+#include "asterisk.h"
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <pthread.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <errno.h>
+#include "asterisk/lock.h"
+#include "asterisk/hashtab.h"
+#include "asterisk/channel.h"
+#include "asterisk/utils.h"
+#include "asterisk/module.h"
+
+struct rc_hist
+{
+ char *desc;
+ struct rc_hist *next;
+};
+
+struct rc_obj /* short for refcounted object */
+{
+ unsigned int addr;
+ unsigned int count; /* this plus addr makes each entry unique, starts at 1 */
+ int last_count; /* count 1 objects will record how many other objects had the same addr */
+ int destroy_count;
+ int total_refcount;
+ struct rc_hist *hist;
+ struct rc_hist *last;
+};
+
+static unsigned int hashtab_hash_rc(const void *obj)
+{
+ const struct rc_obj *rc = obj;
+ return rc->addr + rc->count; /* it's addr will make a FINE hash */
+}
+
+static int hashtab_compare_rc(const void *a, const void *b)
+{
+ const struct rc_obj *rca = a;
+ const struct rc_obj *rcb = b;
+ if (rca->addr == rcb->addr && rca->count == rcb->count)
+ return 0;
+ else
+ return 1;
+}
+
+
+static struct rc_obj *alloc_obj(unsigned int addr, unsigned int count)
+{
+ struct rc_obj *x = calloc(1,sizeof(struct rc_obj));
+ x->addr = addr;
+ x->count = count;
+ x->last_count = 1;
+ x->total_refcount = 1;
+ return x;
+}
+
+static void add_to_hist(char *buffer, struct rc_obj *obj)
+{
+ struct rc_hist *y = calloc(1,sizeof(struct rc_hist));
+ y->desc = strdup(buffer);
+ if (obj->last) {
+ obj->last->next = y;
+ obj->last = y;
+ } else {
+ obj->hist = obj->last = y;
+ }
+}
+
+
+
+int main(int argc,char **argv)
+{
+ char linebuffer[300];
+ FILE *ifile = fopen("/tmp/refs", "r");
+ char *t;
+ unsigned int un;
+ struct rc_obj *curr_obj, *count1_obj;
+ struct rc_obj lookup;
+ struct ast_hashtab_iter *it;
+ struct ast_hashtab *objhash;
+
+ if (!ifile) {
+ printf("Sorry, Cannot open /tmp/refs!\n");
+ exit(10);
+ }
+
+ objhash = ast_hashtab_create(9000, hashtab_compare_rc, ast_hashtab_resize_java, ast_hashtab_newsize_java, hashtab_hash_rc, 1);
+
+ while (fgets(linebuffer, sizeof(linebuffer), ifile)) {
+ /* collect data about the entry */
+ un = strtoul(linebuffer, &t, 16);
+ lookup.addr = un;
+ lookup.count = 1;
+
+ count1_obj = ast_hashtab_lookup(objhash, &lookup);
+
+ if (count1_obj) {
+ /* there IS a count1 obj, so let's see which one we REALLY want */
+ if (*(t+1) == '=') {
+ /* start a new object! */
+ curr_obj = alloc_obj(un, ++count1_obj->last_count);
+ /* put it in the hashtable */
+ ast_hashtab_insert_safe(objhash, curr_obj);
+ } else {
+ if (count1_obj->last_count > 1) {
+ lookup.count = count1_obj->last_count;
+ curr_obj = ast_hashtab_lookup(objhash, &lookup);
+ } else {
+ curr_obj = count1_obj;
+ }
+
+ }
+
+ } else {
+ /* NO obj at ALL? -- better make one! */
+ if (*(t+1) != '=') {
+ printf("BAD: object %x appears without previous allocation marker!\n", count1_obj->addr);
+ }
+ curr_obj = count1_obj = alloc_obj(un, 1);
+ /* put it in the hashtable */
+ ast_hashtab_insert_safe(objhash, curr_obj);
+
+ }
+
+ if (*(t+1) == '+' || *(t+1) == '-' ) {
+ curr_obj->total_refcount += strtol(t+1, NULL, 10);
+ } else if (*(t+1) == '*') {
+ curr_obj->destroy_count++;
+ }
+
+ add_to_hist(linebuffer, curr_obj);
+ }
+ fclose(ifile);
+
+ /* traverse the objects and check for problems */
+ it = ast_hashtab_start_traversal(objhash);
+ while ((curr_obj = ast_hashtab_next(it))) {
+ if (curr_obj->total_refcount != 0 || curr_obj->destroy_count != 1) {
+ struct rc_hist *h;
+ if (curr_obj->total_refcount != 0)
+ printf("Problem: net Refcount not zero for object %x\n", curr_obj->addr);
+ if (curr_obj->destroy_count > 1 )
+ printf("Problem: Object %x destroyed more than once!\n", curr_obj->addr);
+ printf("Object %x history:\n", curr_obj->addr);
+ for(h=curr_obj->hist;h;h=h->next) {
+ printf(" %s", h->desc);
+ }
+ printf("==============\n");
+ }
+ }
+ ast_hashtab_end_traversal(it);
+ return 0;
+}
+
+
+/* stub routines to satisfy linking with asterisk subcomponents */
+
+int ast_add_profile(const char *x, uint64_t scale)
+{
+ return 0;
+}
+
+int ast_loader_register(int (*updater)(void))
+{
+ return 1;
+}
+
+int ast_loader_unregister(int (*updater)(void))
+{
+ return 1;
+}
+void ast_module_register(const struct ast_module_info *x)
+{
+}
+
+void ast_module_unregister(const struct ast_module_info *x)
+{
+}
+
+
+void ast_register_file_version(const char *file, const char *version)
+{
+}
+
+void ast_unregister_file_version(const char *file)
+{
+
+}
+
+#undef ast_mark
+
+int64_t ast_mark(int x, int start1_stop0)
+{
+ return 0;
+}
+
+void ast_log(int level, const char *file, int line, const char *function, const char *fmt, ...)
+{
+ va_list vars;
+ va_start(vars,fmt);
+ printf("LOG: lev:%d file:%s line:%d func: %s ",
+ level, file, line, function);
+ vprintf(fmt, vars);
+ fflush(stdout);
+ va_end(vars);
+}
+
+void ast_verbose(const char *fmt, ...)
+{
+ va_list vars;
+ va_start(vars,fmt);
+
+ printf("VERBOSE: ");
+ vprintf(fmt, vars);
+ fflush(stdout);
+ va_end(vars);
+}
+
+void ast_register_thread(char *name)
+{
+
+}
+
+void ast_unregister_thread(void *id)
+{
+}