[asterisk-commits] rmudgett: trunk r376575 - in /trunk: channels/ include/asterisk/ main/ tests/

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Wed Nov 21 12:33:22 CST 2012


Author: rmudgett
Date: Wed Nov 21 12:33:16 2012
New Revision: 376575

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=376575
Log:
Add red-black tree container type to astobj2.

* Add red-black tree container type.

* Add CLI command "astobj2 container dump <name>"

* Added ao2_container_dump() so the container could be dumped by other
modules for debugging purposes.

* Changed ao2_container_stats() so it can be used by other modules like
ao2_container_check() for debugging purposes.

* Updated the unit tests to check red-black tree containers.

(closes issue ASTERISK-19970)
Reported by: rmudgett
Tested by: rmudgett

Review: https://reviewboard.asterisk.org/r/2110/

Modified:
    trunk/channels/chan_iax2.c
    trunk/include/asterisk/astobj2.h
    trunk/include/asterisk/test.h
    trunk/main/astobj2.c
    trunk/main/channel.c
    trunk/main/test.c
    trunk/tests/test_astobj2.c

Modified: trunk/channels/chan_iax2.c
URL: http://svnview.digium.com/svn/asterisk/trunk/channels/chan_iax2.c?view=diff&rev=376575&r1=376574&r2=376575
==============================================================================
--- trunk/channels/chan_iax2.c (original)
+++ trunk/channels/chan_iax2.c Wed Nov 21 12:33:16 2012
@@ -2774,6 +2774,10 @@
 
 static int callno_hash(const void *obj, const int flags)
 {
+	/*
+	 * XXX A hash function should always return the same value for
+	 * the same inputs.
+	 */
 	return abs(ast_random());
 }
 
@@ -2781,6 +2785,15 @@
 {
 	uint16_t i;
 
+	/*!
+	 * \todo XXX A different method of randomly picking an available
+	 * IAX2 callno needs to be devised.
+	 *
+	 * A hash function should always return the same value for the
+	 * same inputs.  This game with the hash function prevents
+	 * astob2.c from generically checking the integrity of hash
+	 * containers while the system runs.
+	 */
 	if (!(callno_pool = ao2_container_alloc(CALLNO_POOL_BUCKETS, callno_hash, NULL))) {
 		return -1;
 	}

Modified: trunk/include/asterisk/astobj2.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/astobj2.h?view=diff&rev=376575&r1=376574&r2=376575
==============================================================================
--- trunk/include/asterisk/astobj2.h (original)
+++ trunk/include/asterisk/astobj2.h Wed Nov 21 12:33:16 2012
@@ -872,6 +872,10 @@
 	 * not found in the starting bucket defined by the hash value on
 	 * the argument.
 	 *
+	 * For rbtree containers, if the search key is not in the
+	 * container, the search will start either at the first item
+	 * before the search key or the first item after the search key.
+	 *
 	 * \note The supplied ao2_callback_fn is called for every node
 	 * in the container from the starting point.
 	 */
@@ -932,6 +936,24 @@
 	OBJ_ORDER_ASCENDING = (0 << 8),
 	/*! \brief Traverse in descending order (Last to first container object) */
 	OBJ_ORDER_DESCENDING = (1 << 8),
+	/*!
+	 * \brief Traverse in pre-order (Node then children, for tree container)
+	 *
+	 * \note For non-tree containers, it is up to the container type
+	 * to make the best interpretation of the order.  For list and
+	 * hash containers, this also means ascending order because a
+	 * binary tree can degenerate into a list.
+	 */
+	OBJ_ORDER_PRE = (2 << 8),
+	/*!
+	 * \brief Traverse in post-order (Children then node, for tree container)
+	 *
+	 * \note For non-tree containers, it is up to the container type
+	 * to make the best interpretation of the order.  For list and
+	 * hash containers, this also means descending order because a
+	 * binary tree can degenerate into a list.
+	 */
+	OBJ_ORDER_POST = (3 << 8),
 };
 
 /*!
@@ -1184,6 +1206,49 @@
 	unsigned int container_options, ao2_sort_fn *sort_fn, ao2_callback_fn *cmp_fn,
 	const char *tag, const char *file, int line, const char *func, int ref_debug);
 
+/*!
+ * \brief Allocate and initialize a red-black tree container.
+ *
+ * \param ao2_options Container ao2 object options (See enum ao2_alloc_opts)
+ * \param container_options Container behaviour options (See enum ao2_container_opts)
+ * \param sort_fn Pointer to a sort function.
+ * \param cmp_fn Pointer to a compare function used by ao2_find. (NULL to match everything)
+ * \param tag used for debugging.
+ *
+ * \return A pointer to a struct container.
+ *
+ * \note Destructor is set implicitly.
+ */
+
+#if defined(REF_DEBUG)
+
+#define ao2_t_container_alloc_rbtree(ao2_options, container_options, sort_fn, cmp_fn, tag) \
+	__ao2_container_alloc_rbtree_debug((ao2_options), (container_options), (sort_fn), (cmp_fn), (tag),  __FILE__, __LINE__, __PRETTY_FUNCTION__, 1)
+#define ao2_container_alloc_rbtree(ao2_options, container_options, , sort_fn, cmp_fn) \
+	__ao2_container_alloc_rbtree_debug((ao2_options), (container_options), (sort_fn), (cmp_fn), "",  __FILE__, __LINE__, __PRETTY_FUNCTION__, 1)
+
+#elif defined(__AST_DEBUG_MALLOC)
+
+#define ao2_t_container_alloc_rbtree(ao2_options, container_options, sort_fn, cmp_fn, tag) \
+	__ao2_container_alloc_rbtree_debug((ao2_options), (container_options), (sort_fn), (cmp_fn), (tag),  __FILE__, __LINE__, __PRETTY_FUNCTION__, 0)
+#define ao2_container_alloc_rbtree(ao2_options, container_options, sort_fn, cmp_fn) \
+	__ao2_container_alloc_rbtree_debug((ao2_options), (container_options), (sort_fn), (cmp_fn), "",  __FILE__, __LINE__, __PRETTY_FUNCTION__, 0)
+
+#else
+
+#define ao2_t_container_alloc_rbtree(ao2_options, container_options, sort_fn, cmp_fn, tag) \
+	__ao2_container_alloc_rbtree((ao2_options), (container_options), (sort_fn), (cmp_fn))
+#define ao2_container_alloc_rbtree(ao2_options, container_options, sort_fn, cmp_fn) \
+	__ao2_container_alloc_rbtree((ao2_options), (container_options), (sort_fn), (cmp_fn))
+
+#endif
+
+struct ao2_container *__ao2_container_alloc_rbtree(unsigned int ao2_options, unsigned int container_options,
+	ao2_sort_fn *sort_fn, ao2_callback_fn *cmp_fn);
+struct ao2_container *__ao2_container_alloc_rbtree_debug(unsigned int ao2_options, unsigned int container_options,
+	ao2_sort_fn *sort_fn, ao2_callback_fn *cmp_fn,
+	const char *tag, const char *file, int line, const char *func, int ref_debug);
+
 /*! \brief
  * Returns the number of elements in a container.
  */
@@ -1242,6 +1307,58 @@
 #endif
 
 /*!
+ * \brief Print output.
+ * \since 12.0.0
+ *
+ * \param where User data pointer needed to determine where to put output.
+ * \param fmt printf type format string.
+ *
+ * \return Nothing
+ */
+typedef void (ao2_prnt_fn)(void *where, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
+
+/*!
+ * \brief Print object key.
+ * \since 12.0.0
+ *
+ * \param v_obj A pointer to the object we want the key printed.
+ * \param where User data needed by prnt to determine where to put output.
+ * \param prnt Print output callback function to use.
+ *
+ * \return Nothing
+ */
+typedef void (ao2_prnt_obj_fn)(void *v_obj, void *where, ao2_prnt_fn *prnt);
+
+/*!
+ * \brief Display contents of the specified container.
+ * \since 12.0.0
+ *
+ * \param self Container to dump.
+ * \param flags OBJ_NOLOCK if a lock is already held on the container.
+ * \param name Container name.  (NULL if anonymous)
+ * \param where User data needed by prnt to determine where to put output.
+ * \param prnt Print output callback function to use.
+ * \param prnt_obj Callback function to print the given object's key. (NULL if not available)
+ *
+ * \return Nothing
+ */
+void ao2_container_dump(struct ao2_container *self, enum search_flags flags, const char *name, void *where, ao2_prnt_fn *prnt, ao2_prnt_obj_fn *prnt_obj);
+
+/*!
+ * \brief Display statistics of the specified container.
+ * \since 12.0.0
+ *
+ * \param self Container to display statistics.
+ * \param flags OBJ_NOLOCK if a lock is already held on the container.
+ * \param name Container name.  (NULL if anonymous)
+ * \param where User data needed by prnt to determine where to put output.
+ * \param prnt Print output callback function to use.
+ *
+ * \return Nothing
+ */
+void ao2_container_stats(struct ao2_container *self, enum search_flags flags, const char *name, void *where, ao2_prnt_fn *prnt);
+
+/*!
  * \brief Perform an integrity check on the specified container.
  * \since 12.0.0
  *
@@ -1259,11 +1376,12 @@
  *
  * \param name Name to register the container under.
  * \param self Container to register.
+ * \param prnt_obj Callback function to print the given object's key. (NULL if not available)
  *
  * \retval 0 on success.
  * \retval -1 on error.
  */
-int ao2_container_register(const char *name, struct ao2_container *self);
+int ao2_container_register(const char *name, struct ao2_container *self, ao2_prnt_obj_fn *prnt_obj);
 
 /*!
  * \brief Unregister a container for CLI stats and integrity check.

Modified: trunk/include/asterisk/test.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/test.h?view=diff&rev=376575&r1=376574&r2=376575
==============================================================================
--- trunk/include/asterisk/test.h (original)
+++ trunk/include/asterisk/test.h Wed Nov 21 12:33:16 2012
@@ -127,6 +127,7 @@
 #define AST_TEST_REGISTER(cb)
 #define AST_TEST_UNREGISTER(cb)
 #define ast_test_status_update(a,b,c...)
+#define ast_test_debug(test, fmt, ...)	ast_cli		/* Dummy function that should not be called. */
 
 #endif
 
@@ -256,6 +257,17 @@
 int ast_test_register(ast_test_cb_t *cb);
 
 /*!
+ * \brief Unit test debug output.
+ * \since 12.0.0
+ *
+ * \param test Unit test control structure.
+ * \param fmt printf type format string.
+ *
+ * \return Nothing
+ */
+void ast_test_debug(struct ast_test *test, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
+
+/*!
  * \brief update test's status during testing.
  *
  * \param test currently executing test

Modified: trunk/main/astobj2.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/astobj2.c?view=diff&rev=376575&r1=376574&r2=376575
==============================================================================
--- trunk/main/astobj2.c (original)
+++ trunk/main/astobj2.c Wed Nov 21 12:33:16 2012
@@ -95,6 +95,12 @@
 	struct __priv_data priv_data;
 	void *user_data[0];
 };
+
+#if defined(AST_DEVMODE)
+#define AO2_DEVMODE_STAT(stat)	stat
+#else
+#define AO2_DEVMODE_STAT(stat)
+#endif	/* defined(AST_DEVMODE) */
 
 #ifdef AO2_DEBUG
 struct ao2_stats {
@@ -765,6 +771,8 @@
 enum ao2_container_rtti {
 	/*! This is a hash container */
 	AO2_CONTAINER_RTTI_HASH,
+	/*! This is a red-black tree container */
+	AO2_CONTAINER_RTTI_RBTREE,
 };
 
 /*!
@@ -892,17 +900,29 @@
 typedef struct ao2_container_node *(*ao2_iterator_next_fn)(struct ao2_container *self, struct ao2_container_node *prev, enum ao2_iterator_flags flags);
 
 /*!
+ * \brief Display contents of the specified container.
+ *
+ * \param self Container to dump.
+ * \param where User data needed by prnt to determine where to put output.
+ * \param prnt Print output callback function to use.
+ * \param prnt_obj Callback function to print the given object's key. (NULL if not available)
+ *
+ * \return Nothing
+ */
+typedef void (*ao2_container_display)(struct ao2_container *self, void *where, ao2_prnt_fn *prnt, ao2_prnt_obj_fn *prnt_obj);
+
+/*!
  * \brief Display statistics of the specified container.
  *
  * \param self Container to display statistics.
- * \param fd File descriptor to send output.
+ * \param where User data needed by prnt to determine where to put output.
  * \param prnt Print output callback function to use.
  *
  * \note The container is already locked for reading.
  *
  * \return Nothing
  */
-typedef void (*ao2_container_statistics)(struct ao2_container *self, int fd, void (*prnt)(int fd, const char *fmt, ...) __attribute__((format(printf, 2, 3))));
+typedef void (*ao2_container_statistics)(struct ao2_container *self, void *where, ao2_prnt_fn *prnt);
 
 /*!
  * \brief Perform an integrity check on the specified container.
@@ -939,6 +959,8 @@
 	/*! Find the next iteration element in the container. */
 	ao2_iterator_next_fn iterator_next;
 #if defined(AST_DEVMODE)
+	/*! Display container contents. (Method for debug purposes) */
+	ao2_container_display dump;
 	/*! Display container debug statistics. (Method for debug purposes) */
 	ao2_container_statistics stats;
 	/*! Perform an integrity check on the container. (Method for debug purposes) */
@@ -1041,16 +1063,37 @@
 	res = 0;
 	node = self->v_table->new_node(self, obj_new, tag, file, line, func);
 	if (node) {
+#if defined(AO2_DEBUG) && defined(AST_DEVMODE)
+		switch (self->v_table->type) {
+		case AO2_CONTAINER_RTTI_HASH:
+			if (!self->sort_fn) {
+				/*
+				 * XXX chan_iax2 plays games with the hash function so we cannot
+				 * routinely do an integrity check on this type of container.
+				 * chan_iax2 should be changed to not abuse the hash function.
+				 */
+				break;
+			}
+			/* Fall through. */
+		case AO2_CONTAINER_RTTI_RBTREE:
+			if (ao2_container_check(self, OBJ_NOLOCK)) {
+				ast_log(LOG_ERROR, "Container integrity failed before insert.\n");
+			}
+			break;
+		}
+#endif	/* defined(AO2_DEBUG) && defined(AST_DEVMODE) */
 		/* Insert the new node. */
 		switch (self->v_table->insert(self, node)) {
 		case AO2_CONTAINER_INSERT_NODE_INSERTED:
 			node->is_linked = 1;
 			ast_atomic_fetchadd_int(&self->elements, 1);
 #if defined(AST_DEVMODE)
-			++self->nodes;
+			AO2_DEVMODE_STAT(++self->nodes);
 			switch (self->v_table->type) {
 			case AO2_CONTAINER_RTTI_HASH:
 				hash_ao2_link_node_stat(self, node);
+				break;
+			case AO2_CONTAINER_RTTI_RBTREE:
 				break;
 			}
 #endif	/* defined(AST_DEVMODE) */
@@ -1064,6 +1107,27 @@
 			__ao2_ref(node, -1);
 			break;
 		}
+#if defined(AO2_DEBUG) && defined(AST_DEVMODE)
+		if (res) {
+			switch (self->v_table->type) {
+			case AO2_CONTAINER_RTTI_HASH:
+				if (!self->sort_fn) {
+					/*
+					 * XXX chan_iax2 plays games with the hash function so we cannot
+					 * routinely do an integrity check on this type of container.
+					 * chan_iax2 should be changed to not abuse the hash function.
+					 */
+					break;
+				}
+				/* Fall through. */
+			case AO2_CONTAINER_RTTI_RBTREE:
+				if (ao2_container_check(self, OBJ_NOLOCK)) {
+					ast_log(LOG_ERROR, "Container integrity failed after insert.\n");
+				}
+				break;
+			}
+		}
+#endif	/* defined(AO2_DEBUG) && defined(AST_DEVMODE) */
 	}
 
 	if (flags & OBJ_NOLOCK) {
@@ -1329,6 +1393,8 @@
 				case AO2_CONTAINER_RTTI_HASH:
 					hash_ao2_unlink_node_stat(self, node);
 					break;
+				case AO2_CONTAINER_RTTI_RBTREE:
+					break;
 				}
 #endif	/* defined(AST_DEVMODE) */
 
@@ -1566,6 +1632,8 @@
 			case AO2_CONTAINER_RTTI_HASH:
 				hash_ao2_unlink_node_stat(iter->c, node);
 				break;
+			case AO2_CONTAINER_RTTI_RBTREE:
+				break;
 			}
 #endif	/* defined(AST_DEVMODE) */
 
@@ -1764,30 +1832,51 @@
 	return clone;
 }
 
-#if defined(AST_DEVMODE)
-/*!
- * \internal
- * \brief Display statistics of the specified container.
- * \since 12.0.0
- *
- * \param self Container to display statistics.
- * \param fd File descriptor to send output.
- * \param prnt Print output callback function to use.
- *
- * \return Nothing
- */
-static void ao2_container_stats(struct ao2_container *self, int fd, void (*prnt)(int fd, const char *fmt, ...) __attribute__((format(printf, 2, 3))))
+void ao2_container_dump(struct ao2_container *self, enum search_flags flags, const char *name, void *where, ao2_prnt_fn *prnt, ao2_prnt_obj_fn *prnt_obj)
 {
 	if (!INTERNAL_OBJ(self) || !self->v_table) {
-		prnt(fd, "Invalid container\n");
+		prnt(where, "Invalid container\n");
 		ast_assert(0);
 		return;
 	}
 
-	ao2_rdlock(self);
-	prnt(fd, "Number of objects: %d\n", self->elements);
-	prnt(fd, "Number of nodes: %d\n", self->nodes);
-	prnt(fd, "Number of empty nodes: %d\n", self->nodes - self->elements);
+	if (!(flags & OBJ_NOLOCK)) {
+		ao2_rdlock(self);
+	}
+	if (name) {
+		prnt(where, "Container name: %s\n", name);
+	}
+#if defined(AST_DEVMODE)
+	if (self->v_table->dump) {
+		self->v_table->dump(self, where, prnt, prnt_obj);
+	} else
+#endif	/* defined(AST_DEVMODE) */
+	{
+		prnt(where, "Container dump not available.\n");
+	}
+	if (!(flags & OBJ_NOLOCK)) {
+		ao2_unlock(self);
+	}
+}
+
+void ao2_container_stats(struct ao2_container *self, enum search_flags flags, const char *name, void *where, ao2_prnt_fn *prnt)
+{
+	if (!INTERNAL_OBJ(self) || !self->v_table) {
+		prnt(where, "Invalid container\n");
+		ast_assert(0);
+		return;
+	}
+
+	if (!(flags & OBJ_NOLOCK)) {
+		ao2_rdlock(self);
+	}
+	if (name) {
+		prnt(where, "Container name: %s\n", name);
+	}
+	prnt(where, "Number of objects: %d\n", self->elements);
+#if defined(AST_DEVMODE)
+	prnt(where, "Number of nodes: %d\n", self->nodes);
+	prnt(where, "Number of empty nodes: %d\n", self->nodes - self->elements);
 	/*
 	 * XXX
 	 * If the max_empty_nodes count gets out of single digits you
@@ -1797,13 +1886,15 @@
 	 * Empty nodes do not harm the container but they do make
 	 * container operations less efficient.
 	 */
-	prnt(fd, "Maximum empty nodes: %d\n", self->max_empty_nodes);
+	prnt(where, "Maximum empty nodes: %d\n", self->max_empty_nodes);
 	if (self->v_table->stats) {
-		self->v_table->stats(self, fd, prnt);
-	}
-	ao2_unlock(self);
-}
+		self->v_table->stats(self, where, prnt);
+	}
 #endif	/* defined(AST_DEVMODE) */
+	if (!(flags & OBJ_NOLOCK)) {
+		ao2_unlock(self);
+	}
+}
 
 int ao2_container_check(struct ao2_container *self, enum search_flags flags)
 {
@@ -1945,6 +2036,9 @@
  * The container node unlinks itself from the container as part
  * of its destruction.  The node must be destroyed while the
  * container is already locked.
+ *
+ * \note The container must be locked when the node is
+ * unreferenced.
  *
  * \return Nothing
  */
@@ -1972,12 +2066,21 @@
 		my_container = (struct ao2_container_hash *) doomed->common.my_container;
 		adjust_lock(my_container, AO2_LOCK_REQ_WRLOCK, 1);
 
+#if defined(AO2_DEBUG) && defined(AST_DEVMODE)
+		/*
+		 * XXX chan_iax2 plays games with the hash function so we cannot
+		 * routinely do an integrity check on this type of container.
+		 * chan_iax2 should be changed to not abuse the hash function.
+		 */
+		if (!my_container->common.destroying
+			&& my_container->common.sort_fn
+			&& ao2_container_check(doomed->common.my_container, OBJ_NOLOCK)) {
+			ast_log(LOG_ERROR, "Container integrity failed before node deletion.\n");
+		}
+#endif	/* defined(AO2_DEBUG) && defined(AST_DEVMODE) */
 		bucket = &my_container->buckets[doomed->my_bucket];
 		AST_DLLIST_REMOVE(&bucket->list, doomed, links);
-
-#if defined(AST_DEVMODE)
-		--my_container->common.nodes;
-#endif	/* defined(AST_DEVMODE) */
+		AO2_DEVMODE_STAT(--my_container->common.nodes);
 	}
 
 	/*
@@ -2173,9 +2276,11 @@
 
 	/* Determine traversal order. */
 	switch (flags & OBJ_ORDER_MASK) {
+	case OBJ_ORDER_POST:
 	case OBJ_ORDER_DESCENDING:
 		state->descending = 1;
 		break;
+	case OBJ_ORDER_PRE:
 	case OBJ_ORDER_ASCENDING:
 	default:
 		break;
@@ -2711,18 +2816,72 @@
 #if defined(AST_DEVMODE)
 /*!
  * \internal
+ * \brief Display contents of the specified container.
+ * \since 12.0.0
+ *
+ * \param self Container to dump.
+ * \param where User data needed by prnt to determine where to put output.
+ * \param prnt Print output callback function to use.
+ * \param prnt_obj Callback function to print the given object's key. (NULL if not available)
+ *
+ * \return Nothing
+ */
+static void hash_ao2_dump(struct ao2_container_hash *self, void *where, ao2_prnt_fn *prnt, ao2_prnt_obj_fn *prnt_obj)
+{
+#define FORMAT  "%6s, %16s, %16s, %16s, %16s, %s\n"
+#define FORMAT2 "%6d, %16p, %16p, %16p, %16p, "
+
+	int bucket;
+	int suppressed_buckets = 0;
+	struct hash_bucket_node *node;
+
+	prnt(where, "Number of buckets: %d\n\n", self->n_buckets);
+
+	prnt(where, FORMAT, "Bucket", "Node", "Prev", "Next", "Obj", "Key");
+	for (bucket = 0; bucket < self->n_buckets; ++bucket) {
+		node = AST_DLLIST_FIRST(&self->buckets[bucket].list);
+		if (node) {
+			suppressed_buckets = 0;
+			do {
+				prnt(where, FORMAT2,
+					bucket,
+					node,
+					AST_DLLIST_PREV(node, links),
+					AST_DLLIST_NEXT(node, links),
+					node->common.obj);
+				if (node->common.obj && prnt_obj) {
+					prnt_obj(node->common.obj, where, prnt);
+				}
+				prnt(where, "\n");
+
+				node = AST_DLLIST_NEXT(node, links);
+			} while (node);
+		} else if (!suppressed_buckets) {
+			suppressed_buckets = 1;
+			prnt(where, "...\n");
+		}
+	}
+
+#undef FORMAT
+#undef FORMAT2
+}
+#endif	/* defined(AST_DEVMODE) */
+
+#if defined(AST_DEVMODE)
+/*!
+ * \internal
  * \brief Display statistics of the specified container.
  * \since 12.0.0
  *
  * \param self Container to display statistics.
- * \param fd File descriptor to send output.
+ * \param where User data needed by prnt to determine where to put output.
  * \param prnt Print output callback function to use.
  *
  * \note The container is already locked for reading.
  *
  * \return Nothing
  */
-static void hash_ao2_stats(struct ao2_container_hash *self, int fd, void (*prnt)(int fd, const char *fmt, ...) __attribute__((format(printf, 2, 3))))
+static void hash_ao2_stats(struct ao2_container_hash *self, void *where, ao2_prnt_fn *prnt)
 {
 #define FORMAT  "%10.10s %10.10s %10.10s\n"
 #define FORMAT2 "%10d %10d %10d\n"
@@ -2730,17 +2889,17 @@
 	int bucket;
 	int suppressed_buckets = 0;
 
-	prnt(fd, "Number of buckets: %d\n\n", self->n_buckets);
-
-	prnt(fd, FORMAT, "Bucket", "Objects", "Max");
+	prnt(where, "Number of buckets: %d\n\n", self->n_buckets);
+
+	prnt(where, FORMAT, "Bucket", "Objects", "Max");
 	for (bucket = 0; bucket < self->n_buckets; ++bucket) {
 		if (self->buckets[bucket].max_elements) {
-			prnt(fd, FORMAT2, bucket, self->buckets[bucket].elements,
+			suppressed_buckets = 0;
+			prnt(where, FORMAT2, bucket, self->buckets[bucket].elements,
 				self->buckets[bucket].max_elements);
-			suppressed_buckets = 0;
 		} else if (!suppressed_buckets) {
 			suppressed_buckets = 1;
-			prnt(fd, "...\n");
+			prnt(where, "...\n");
 		}
 	}
 
@@ -2815,6 +2974,11 @@
 			/* Check backward link. */
 			prev = AST_DLLIST_PREV(node, links);
 			if (prev) {
+				if (prev == node) {
+					ast_log(LOG_ERROR, "Bucket %d list node's prev pointer points to itself!\n",
+						bucket);
+					return -1;
+				}
 				if (node != AST_DLLIST_NEXT(prev, links)) {
 					ast_log(LOG_ERROR, "Bucket %d list node's prev node does not link back!\n",
 						bucket);
@@ -2829,6 +2993,11 @@
 			/* Check forward link. */
 			next = AST_DLLIST_NEXT(node, links);
 			if (next) {
+				if (next == node) {
+					ast_log(LOG_ERROR, "Bucket %d list node's next pointer points to itself!\n",
+						bucket);
+					return -1;
+				}
 				if (node != AST_DLLIST_PREV(next, links)) {
 					ast_log(LOG_ERROR, "Bucket %d list node's next node does not link back!\n",
 						bucket);
@@ -2918,6 +3087,7 @@
 	.iterator_next = (ao2_iterator_next_fn) hash_ao2_iterator_next,
 	.destroy = (ao2_container_destroy_fn) hash_ao2_destroy,
 #if defined(AST_DEVMODE)
+	.dump = (ao2_container_display) hash_ao2_dump,
 	.stats = (ao2_container_statistics) hash_ao2_stats,
 	.integrity = (ao2_container_integrity) hash_ao2_integrity,
 #endif	/* defined(AST_DEVMODE) */
@@ -3019,6 +3189,2072 @@
 {
 	return __ao2_container_alloc_hash_debug(ao2_options, container_options, 1, NULL,
 		sort_fn, cmp_fn, tag, file, line, func, ref_debug);
+}
+
+/*!
+ * A structure to hold the object held by the container and
+ * where it is located in it.
+ *
+ * A red-black tree has the following properties:
+ *
+ * 1) Every node is either black or red.
+ *
+ * 2) The root is black.
+ *
+ * 3) If a node has a NULL child, that "child" is considered
+ * black.
+ *
+ * 4) If a node is red, then both of its children are black.
+ *
+ * 5) Every path from a node to a descendant NULL child has the
+ * same number of black nodes.  (Including the black NULL
+ * child.)
+ */
+struct rbtree_node {
+	/*!
+	 * \brief Items common to all container nodes.
+	 * \note Must be first in the specific node struct.
+	 */
+	struct ao2_container_node common;
+	/*! Parent node of this node. NULL if this is the root node. */
+	struct rbtree_node *parent;
+	/*! Left child node of this node.  NULL if does not have this child. */
+	struct rbtree_node *left;
+	/*! Right child node of this node.  NULL if does not have this child. */
+	struct rbtree_node *right;
+	/*! TRUE if the node is red. */
+	unsigned int is_red:1;
+};
+
+/*!
+ * A rbtree container in addition to values common to all
+ * container types, stores the pointer to the root node of the
+ * tree.
+ */
+struct ao2_container_rbtree {
+	/*!
+	 * \brief Items common to all containers.
+	 * \note Must be first in the specific container struct.
+	 */
+	struct ao2_container common;
+	/*! Root node of the tree.  NULL if the tree is empty. */
+	struct rbtree_node *root;
+#if defined(AST_DEVMODE)
+	struct {
+		/*! Fixup insert left cases 1-3 */
+		int fixup_insert_left[3];
+		/*! Fixup insert right cases 1-3 */
+		int fixup_insert_right[3];
+		/*! Fixup delete left cases 1-4 */
+		int fixup_delete_left[4];
+		/*! Fixup delete right cases 1-4 */
+		int fixup_delete_right[4];
+		/*! Deletion of node with number of children (0-2). */
+		int delete_children[3];
+	} stats;
+#endif	/* defined(AST_DEVMODE) */
+};
+
+/*!
+ * \internal
+ * \brief Get the most left node in the tree.
+ * \since 12.0.0
+ *
+ * \param node Starting node to find the most left node.
+ *
+ * \return Left most node.  Never NULL.
+ */
+static struct rbtree_node *rb_node_most_left(struct rbtree_node *node)
+{
+	while (node->left) {
+		node = node->left;
+	}
+
+	return node;
+}
+
+/*!
+ * \internal
+ * \brief Get the most right node in the tree.
+ * \since 12.0.0
+ *
+ * \param node Starting node to find the most right node.
+ *
+ * \return Right most node.  Never NULL.
+ */
+static struct rbtree_node *rb_node_most_right(struct rbtree_node *node)
+{
+	while (node->right) {
+		node = node->right;
+	}
+
+	return node;
+}
+
+/*!
+ * \internal
+ * \brief Get the next node in ascending sequence.
+ * \since 12.0.0
+ *
+ * \param node Starting node to find the next node.
+ *
+ * \retval node on success.
+ * \retval NULL if no node.
+ */
+static struct rbtree_node *rb_node_next(struct rbtree_node *node)
+{
+	if (node->right) {
+		return rb_node_most_left(node->right);
+	}
+
+	/* Find the parent that the node is a left child of. */
+	while (node->parent) {
+		if (node->parent->left == node) {
+			/* We are the left child.  The parent is the next node. */
+			return node->parent;
+		}
+		node = node->parent;
+	}
+	return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Get the next node in descending sequence.
+ * \since 12.0.0
+ *
+ * \param node Starting node to find the previous node.
+ *
+ * \retval node on success.
+ * \retval NULL if no node.
+ */
+static struct rbtree_node *rb_node_prev(struct rbtree_node *node)
+{
+	if (node->left) {
+		return rb_node_most_right(node->left);
+	}
+
+	/* Find the parent that the node is a right child of. */
+	while (node->parent) {
+		if (node->parent->right == node) {
+			/* We are the right child.  The parent is the previous node. */
+			return node->parent;
+		}
+		node = node->parent;
+	}
+	return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Get the next node in pre-order sequence.
+ * \since 12.0.0
+ *
+ * \param node Starting node to find the next node.
+ *
+ * \retval node on success.
+ * \retval NULL if no node.
+ */
+static struct rbtree_node *rb_node_pre(struct rbtree_node *node)
+{
+	/* Visit the children if the node has any. */
+	if (node->left) {
+		return node->left;
+	}
+	if (node->right) {
+		return node->right;
+	}
+
+	/* Time to go back up. */
+	for (;;) {
+		if (!node->parent) {
+			return NULL;
+		}
+		if (node->parent->left == node && node->parent->right) {
+			/*
+			 * We came up the left child and there's a right child.  Visit
+			 * it.
+			 */
+			return node->parent->right;
+		}
+		node = node->parent;
+	}
+}
+
+/*!
+ * \internal
+ * \brief Get the next node in post-order sequence.
+ * \since 12.0.0
+ *
+ * \param node Starting node to find the next node.
+ *
+ * \retval node on success.
+ * \retval NULL if no node.
+ */
+static struct rbtree_node *rb_node_post(struct rbtree_node *node)
+{
+	/* This node's children have already been visited. */
+	for (;;) {
+		if (!node->parent) {
+			return NULL;
+		}
+		if (node->parent->left == node) {
+			/* We came up the left child. */
+			node = node->parent;
+
+			/*
+			 * Find the right child's left most childless node.
+			 */
+			while (node->right) {
+				node = rb_node_most_left(node->right);
+			}
+
+			/*
+			 * This node's left child has already been visited or it doesn't
+			 * have any children.
+			 */
+			return node;
+		}
+
+		/*
+		 * We came up the right child.
+		 *
+		 * This node's children have already been visited.  Time to
+		 * visit the parent.
+		 */
+		return node->parent;
+	}
+}
+
+/*!
+ * \internal
+ * \brief Get the next non-empty node in ascending sequence.
+ * \since 12.0.0
+ *
+ * \param node Starting node to find the next node.
+ *
+ * \retval node on success.
+ * \retval NULL if no node.
+ */
+static struct rbtree_node *rb_node_next_full(struct rbtree_node *node)
+{
+	for (;;) {
+		node = rb_node_next(node);
+		if (!node || node->common.obj) {
+			return node;
+		}
+	}
+}
+
+/*!
+ * \internal
+ * \brief Get the next non-empty node in descending sequence.
+ * \since 12.0.0
+ *
+ * \param node Starting node to find the previous node.
+ *
+ * \retval node on success.
+ * \retval NULL if no node.
+ */
+static struct rbtree_node *rb_node_prev_full(struct rbtree_node *node)
+{
+	for (;;) {
+		node = rb_node_prev(node);
+		if (!node || node->common.obj) {
+			return node;
+		}
+	}
+}
+
+enum empty_node_direction {
+	GO_LEFT,
+	GO_RIGHT,
+};
+
+/*!
+ * \internal
+ * \brief Determine which way to go from an empty node.
+ * \since 12.0.0
+ *
+ * \param empty Empty node to determine which side obj_right goes on.
+ * \param sort_fn Sort comparison function for non-empty nodes.
+ * \param obj_right pointer to the (user-defined part) of an object.
+ * \param flags flags from ao2_callback()
+ *   OBJ_POINTER - if set, 'obj_right', is an object.
+ *   OBJ_KEY - if set, 'obj_right', is a search key item that is not an object.
+ *   OBJ_PARTIAL_KEY - if set, 'obj_right', is a partial search key item that is not an object.
+ *
+ * \return enum empty_node_direction to proceed.
+ */
+static enum empty_node_direction rb_find_empty_direction(struct rbtree_node *empty, ao2_sort_fn *sort_fn, void *obj_right, enum search_flags flags)
+{
+	int cmp;
+	struct rbtree_node *cur;
+	struct rbtree_node *right_most;
+
+	/* Try for a quick definite go left. */
+	if (!empty->left) {
+		/* The empty node has no left child. */
+		return GO_RIGHT;
+	}
+	right_most = rb_node_most_right(empty->left);
+	if (right_most->common.obj) {
+		cmp = sort_fn(right_most->common.obj, obj_right, flags);
+		if (cmp < 0) {
+			return GO_RIGHT;
+		}
+		return GO_LEFT;
+	}
+
+	/* Try for a quick definite go right. */
+	if (!empty->right) {
+		/* The empty node has no right child. */
+		return GO_LEFT;
+	}
+	cur = rb_node_most_left(empty->right);
+	if (cur->common.obj) {
+		cmp = sort_fn(cur->common.obj, obj_right, flags);
+		if (cmp <= 0) {
+			return GO_RIGHT;
+		}
+		return GO_LEFT;
+	}
+
+	/*
+	 * Have to scan the previous nodes from the right_most node of
+	 * the left subtree for the first non-empty node to determine
+	 * direction.
+	 */
+	cur = right_most;
+	for (;;) {
+		/* Find previous node. */
+		if (cur->left) {
+			cur = rb_node_most_right(cur->left);
+		} else {
+			/* Find the parent that the node is a right child of. */
+			for (;;) {
+				if (cur->parent == empty) {
+					/* The left side of the empty node is all empty nodes. */
+					return GO_RIGHT;
+				}
+				if (cur->parent->right == cur) {
+					/* We are the right child.  The parent is the previous node. */
+					cur = cur->parent;
+					break;
+				}
+				cur = cur->parent;
+			}
+		}
+
+		if (cur->common.obj) {
+			cmp = sort_fn(cur->common.obj, obj_right, flags);
+			if (cmp < 0) {
+				return GO_RIGHT;
+			}
+			return GO_LEFT;
+		}
+	}
+}
+
+/*!
+ * \internal
+ * \brief Tree node rotation left.
+ * \since 12.0.0
+ *
+ * \param self Container holding node.
+ * \param node Node to perform a left rotation with.
+ *
+ *        p                         p
+ *        |     Left rotation       |
+ *        N        --->             Ch
+ *       / \                       / \
+ *      a  Ch                     N   c
+ *        / \                    / \
+ *       b   c                  a   b
+ *
+ * N = node
+ * Ch = child
+ * p = parent
+ * a,b,c = other nodes that are unaffected by the rotation.
+ *
+ * \note It is assumed that the node's right child exists.
+ *
+ * \return Nothing
+ */
+static void rb_rotate_left(struct ao2_container_rbtree *self, struct rbtree_node *node)
+{
+	struct rbtree_node *child;	/*!< Node's right child. */
+
+	child = node->right;
+
+	/* Link the node's parent to the child. */
+	if (!node->parent) {
+		/* Node is the root so we get a new root node. */
+		self->root = child;
+	} else if (node->parent->left == node) {
+		/* Node is a left child. */
+		node->parent->left = child;
+	} else {
+		/* Node is a right child. */
+		node->parent->right = child;
+	}
+	child->parent = node->parent;
+
+	/* Link node's right subtree to the child's left subtree. */
+	node->right = child->left;
+	if (node->right) {
+		node->right->parent = node;
+	}
+
+	/* Link the node to the child's left. */
+	node->parent = child;
+	child->left = node;
+}
+
+/*!
+ * \internal
+ * \brief Tree node rotation right.
+ * \since 12.0.0
+ *
+ * \param self Container holding node.
+ * \param node Node to perform a right rotation with.
+ *
+ *        p                         p
+ *        |     Right rotation      |
+ *        Ch                        N
+ *       / \       <---            / \
+ *      a  N                      Ch  c
+ *        / \                    / \
+ *       b   c                  a   b
+ *
+ * N = node
+ * Ch = child
+ * p = parent
+ * a,b,c = other nodes that are unaffected by the rotation.
+ *
+ * \note It is assumed that the node's left child exists.
+ *
+ * \return Nothing
+ */
+static void rb_rotate_right(struct ao2_container_rbtree *self, struct rbtree_node *node)
+{
+	struct rbtree_node *child;	/*!< Node's left child. */
+
+	child = node->left;
+
+	/* Link the node's parent to the child. */
+	if (!node->parent) {
+		/* Node is the root so we get a new root node. */
+		self->root = child;
+	} else if (node->parent->right == node) {
+		/* Node is a right child. */
+		node->parent->right = child;
+	} else {
+		/* Node is a left child. */
+		node->parent->left = child;
+	}
+	child->parent = node->parent;
+
+	/* Link node's left subtree to the child's right subtree. */
+	node->left = child->right;
+	if (node->left) {
+		node->left->parent = node;
+	}
+
+	/* Link the node to the child's right. */
+	node->parent = child;
+	child->right = node;
+}
+
+/*!
+ * \internal
+ * \brief Create an empty copy of this container.
+ * \since 12.0.0
+ *
+ * \param self Container to operate upon.
+ *
+ * \retval empty-clone-container on success.
+ * \retval NULL on error.
+ */
+static struct ao2_container *rb_ao2_alloc_empty_clone(struct ao2_container_rbtree *self)
+{
+	struct astobj2 *orig_obj;
+	unsigned int ao2_options;
+
+	/* Get container ao2 options. */
+	orig_obj = INTERNAL_OBJ(self);
+	if (!orig_obj) {
+		return NULL;
+	}
+	ao2_options = orig_obj->priv_data.options;
+
+	return ao2_t_container_alloc_rbtree(ao2_options, self->common.options,
+		self->common.sort_fn, self->common.cmp_fn, "Clone rbtree container");
+}
+
+/*!
+ * \internal
+ * \brief Create an empty copy of this container. (Debug version)
+ * \since 12.0.0
+ *
+ * \param self Container to operate upon.
+ * \param tag used for debugging.
+ * \param file Debug file name invoked from
+ * \param line Debug line invoked from
+ * \param func Debug function name invoked from
+ * \param ref_debug TRUE if to output a debug reference message.
+ *
+ * \retval empty-clone-container on success.
+ * \retval NULL on error.
+ */
+static struct ao2_container *rb_ao2_alloc_empty_clone_debug(struct ao2_container_rbtree *self, const char *tag, const char *file, int line, const char *func, int ref_debug)
+{
+	struct astobj2 *orig_obj;
+	unsigned int ao2_options;
+
+	/* Get container ao2 options. */
+	orig_obj = INTERNAL_OBJ(self);
+	if (!orig_obj) {
+		return NULL;
+	}
+	ao2_options = orig_obj->priv_data.options;
+
+	return __ao2_container_alloc_rbtree_debug(ao2_options, self->common.options,
+		self->common.sort_fn, self->common.cmp_fn, tag, file, line, func, ref_debug);
+}
+
+/*!
+ * \internal
+ * \brief Fixup the rbtree after deleting a node.
+ * \since 12.0.0
+ *
+ * \param self Container to operate upon.
+ * \param child Child of the node just deleted from the container.
+ *
+ * \note The child must be a dummy black node if there really
+ * was no child of the deleted node.  Otherwise, the caller must
+ * pass in the parent node and which child was deleted.  In
+ * addition, the fixup routine would be more complicated.
+ *
+ * \return Nothing
+ */
+static void rb_delete_fixup(struct ao2_container_rbtree *self, struct rbtree_node *child)
+{
+	struct rbtree_node *sibling;
+
+	while (self->root != child && !child->is_red) {
+		if (child->parent->left == child) {
+			/* Child is a left child. */
+			sibling = child->parent->right;
+			ast_assert(sibling != NULL);
+			if (sibling->is_red) {
+				/* Case 1: The child's sibling is red. */
+				AO2_DEVMODE_STAT(++self->stats.fixup_delete_left[0]);
+				sibling->is_red = 0;
+				child->parent->is_red = 1;
+				rb_rotate_left(self, child->parent);
+				sibling = child->parent->right;
+				ast_assert(sibling != NULL);
+			}
+			/*
+			 * The sibling is black.  A black node must have two children,
+			 * or one red child, or no children.
+			 */
+			if ((!sibling->left || !sibling->left->is_red)
+				&& (!sibling->right || !sibling->right->is_red)) {
+				/*
+				 * Case 2: The sibling is black and both of its children are black.
+				 *
+				 * This case handles the two black children or no children
+				 * possibilities of a black node.
+				 */
+				AO2_DEVMODE_STAT(++self->stats.fixup_delete_left[1]);
+				sibling->is_red = 1;
+				child = child->parent;
+			} else {
+				/* At this point the sibling has at least one red child. */
+				if (!sibling->right || !sibling->right->is_red) {
+					/*
+					 * Case 3: The sibling is black, its left child is red, and its
+					 * right child is black.
+					 */
+					AO2_DEVMODE_STAT(++self->stats.fixup_delete_left[2]);
+					ast_assert(sibling->left != NULL);
+					ast_assert(sibling->left->is_red);
+					sibling->left->is_red = 0;
+					sibling->is_red = 1;
+					rb_rotate_right(self, sibling);
+					sibling = child->parent->right;
+					ast_assert(sibling != NULL);
+				}
+				/* Case 4: The sibling is black and its right child is red. */
+				AO2_DEVMODE_STAT(++self->stats.fixup_delete_left[3]);
+				sibling->is_red = child->parent->is_red;
+				child->parent->is_red = 0;
+				if (sibling->right) {
+					sibling->right->is_red = 0;
+				}
+				rb_rotate_left(self, child->parent);
+				child = self->root;
+			}
+		} else {
+			/* Child is a right child. */
+			sibling = child->parent->left;
+			ast_assert(sibling != NULL);
+			if (sibling->is_red) {
+				/* Case 1: The child's sibling is red. */
+				AO2_DEVMODE_STAT(++self->stats.fixup_delete_right[0]);
+				sibling->is_red = 0;
+				child->parent->is_red = 1;
+				rb_rotate_right(self, child->parent);
+				sibling = child->parent->left;
+				ast_assert(sibling != NULL);
+			}
+			/*
+			 * The sibling is black.  A black node must have two children,
+			 * or one red child, or no children.
+			 */
+			if ((!sibling->right || !sibling->right->is_red)
+				&& (!sibling->left || !sibling->left->is_red)) {
+				/*
+				 * Case 2: The sibling is black and both of its children are black.
+				 *

[... 2009 lines stripped ...]



More information about the asterisk-commits mailing list