[asterisk-commits] dlee: branch group/performance r399764 - in /team/group/performance: ./ apps/...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Tue Sep 24 18:22:32 CDT 2013


Author: dlee
Date: Tue Sep 24 18:22:28 2013
New Revision: 399764

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=399764
Log:
Multiple revisions 399666,399681,399695,399736,399749

........
  r399666 | mjordan | 2013-09-24 12:57:09 -0500 (Tue, 24 Sep 2013) | 72 lines
  
  Fix a performance problem CDRs
  
  There is a large performance price currently in the CDR engine. We currently
  perform two ao2_callback calls on a container that has an entry for every
  channel in the system. This is done to create matching pairs between channels
  in a bridge.
  
  As such, the portion of the CDR logic that this patch deals with is how we
  make pairings when a channel enters a mixing bridge. In general, when a
  channel enters such a bridge, we need to do two things:
   (1) Figure out if anyone in the bridge can be this channel's Party B.
   (2) Make pairings with every other channel in the bridge that is not already
       our Party B.
  
  This is a two step process. In the first step, we look through everyone in the
  bridge and see if they can be our Party B (single_state_process_bridge_enter).
  If they can - yay! We mark our CDR as having gotten a Party B. If not, we keep
  searching. If we don't find one, we wait until someone joins who can be our
  Party B.
  
  Step 2 is where we changed the logic
  (handle_bridge_pairings and bridge_candidate_process). Previously, we would
  first find candidates - those channels in the bridge with us - from the
  active_cdrs_by_channel container. Because a channel could be a candidate if it
  was Party B to an item in the container, the code implemented multiple
  ao2_container callbacks to get all the candidates. We also had to store them
  in another container with some other meta information. This was rather complex
  and costly, particularly if you have 300 Local channels (600 channels!) going
  at once.
  
  Luckily, none of it is needed: when a channel enters a bridge (which is when
  we're figuring all this stuff out), the bridge snapshot tells us the unique
  IDs of everyone already in the bridge. All we need to do is:
   For all channels in the bridge:
     If the channel is us or our Party B that we got in step 1, skip it
     Compare us and the candidate to figure out who is Party A (based on some
         specific rules)
     If we are Party A:
        Make a new CDR for us, append it to our chain, and set the candidate as
            Party B
     If they are Party A:
        If they don't have a Party B:
          Make a new CDR for them, append us to their chain, and us as Party B
        Otherwise:
          Copy us over as Party B on their existing CDR.
  
  This patch does that.
  
  Because we now use channel unique IDs to find the candidates during bridging,
  active_cdrs_by_channel now looks up things using uniqueid instead of channel
  name. This makes the more complex code simpler; it does, however, have the
  drawback that dialplan applications and functions will be slightly slower as
  they have to iterate through the container looking for the CDR by name.
  That's a small price to pay however as the bridging code will be called a lot
  more often.
  
  This patch also does two other minor changes:
   (1) It reduces the container size of the channels in a bridge snapshot to 1.
       In order to be predictable for multi-party bridges, the order of the
       channels in the container must be stable; that is, it must always devolve
       to a linked list.
   (2) CDRs and the multi-party test was updated to show the relationship between
       two dialed channels. You still want to know if they talked - previously,
       dialed channels were always ignored, which is wrong when they have
       managed to get a Party B.
  
  (closes issue ASTERISK-22488)
  Reported by: Richard Mudgett
  
  Review: https://reviewboard.asterisk.org/r/2861/
........
  r399681 | mjordan | 2013-09-24 13:58:46 -0500 (Tue, 24 Sep 2013) | 10 lines
  
  app_queue: Initialize array holding MixMonitor exec options
  
  If the channel variable MONITOR_EXEC is set, app_queue will pass the specified
  execution parameters to the MixMonitor application when a queue is recorded.
  If that channel variable is not set, the buffer that holds the escaped value
  was not being initialized to NULL, and so would be passed to the MixMonitor
  application with garbage. Hilarity ensued as app_mixmonitor attempted to
  execute gobeldy-gook.
........
  r399695 | mjordan | 2013-09-24 14:22:13 -0500 (Tue, 24 Sep 2013) | 4 lines
  
  app_queue: Don't be quite so aggressive in initializing the array
  
  We only need the first character.
........
  r399736 | rmudgett | 2013-09-24 15:34:59 -0500 (Tue, 24 Sep 2013) | 23 lines
  
  chan_iax2: Prevent some needless breaking of the native IAX2 bridge.
  
  * Clean up some twisted code in the iax2_bridge() loop.
  
  * Add AST_CONTROL_VIDUPDATE and AST_CONTROL_SRCCHANGE to a list of frames
  to prevent the native bridge loop from breaking.
  
  * Passing the AST_CONTROL_T38_PARAMETERS frame should also allow FAX over
  a native IAX2 bridge.
  
  (issue ABE-2912)
  
  Review: https://reviewboard.asterisk.org/r/2870/
  ........
  
  Merged revisions 399697 from http://svn.asterisk.org/svn/asterisk/branches/1.8
  ........
  
  Merged revisions 399708 from http://svn.asterisk.org/svn/asterisk/branches/11
  
  For v12 and above this is really just documentation until IAX2 native
  bridging is restored.
........
  r399749 | rmudgett | 2013-09-24 17:50:50 -0500 (Tue, 24 Sep 2013) | 5 lines
  
  astobj2: Made use OBJ_SEARCH_xxx identifiers as field enum values internally.
  
  * Made ao2_unlink to protect itself from stray OBJ_SEARCH_xxx values
  passed in.
........

Merged revisions 399666,399681,399695,399736,399749 from http://svn.asterisk.org/svn/asterisk/branches/12

Modified:
    team/group/performance/   (props changed)
    team/group/performance/apps/app_queue.c
    team/group/performance/channels/chan_iax2.c
    team/group/performance/main/astobj2.c
    team/group/performance/main/cdr.c
    team/group/performance/main/stasis_bridges.c
    team/group/performance/tests/test_cdr.c

Propchange: team/group/performance/
------------------------------------------------------------------------------
Binary property 'branch-11-merged' - no diff available.

Propchange: team/group/performance/
------------------------------------------------------------------------------
--- svnmerge-integrated (original)
+++ svnmerge-integrated Tue Sep 24 18:22:28 2013
@@ -1,1 +1,1 @@
-/branches/12:1-399658 /team/dlee/performance:1-399659 /team/dlee/stasis-forward-optimization:1-399664 /team/dlee/taskprocessor-optimization:1-399654
+/branches/12:1-399753 /team/dlee/performance:1-399659 /team/dlee/stasis-forward-optimization:1-399664 /team/dlee/taskprocessor-optimization:1-399654

Modified: team/group/performance/apps/app_queue.c
URL: http://svnview.digium.com/svn/asterisk/team/group/performance/apps/app_queue.c?view=diff&rev=399764&r1=399763&r2=399764
==============================================================================
--- team/group/performance/apps/app_queue.c (original)
+++ team/group/performance/apps/app_queue.c Tue Sep 24 18:22:28 2013
@@ -5914,6 +5914,8 @@
 	const char *monitor_options;
 	const char *monitor_exec;
 
+	escaped_monitor_exec[0] = '\0';
+
 	if (filename) {
 		escape_and_substitute(qe->chan, filename, escaped_filename, sizeof(escaped_filename));
 	} else {

Modified: team/group/performance/channels/chan_iax2.c
URL: http://svnview.digium.com/svn/asterisk/team/group/performance/channels/chan_iax2.c?view=diff&rev=399764&r1=399763&r2=399764
==============================================================================
--- team/group/performance/channels/chan_iax2.c (original)
+++ team/group/performance/channels/chan_iax2.c Tue Sep 24 18:22:28 2013
@@ -5517,34 +5517,44 @@
 			break;
 		}
 		other = (who == c0) ? c1 : c0;  /* the 'other' channel */
-		if ((f->frametype == AST_FRAME_CONTROL)) {
-			if (f->subclass.integer == AST_CONTROL_PVT_CAUSE_CODE) {
+		if (f->frametype == AST_FRAME_CONTROL) {
+			switch (f->subclass.integer) {
+			case AST_CONTROL_VIDUPDATE:
+			case AST_CONTROL_SRCUPDATE:
+			case AST_CONTROL_SRCCHANGE:
+			case AST_CONTROL_T38_PARAMETERS:
+				ast_write(other, f);
+				break;
+			case AST_CONTROL_PVT_CAUSE_CODE:
 				ast_channel_hangupcause_hash_set(other, f->data.ptr, f->datalen);
-			} else if (f->subclass.integer != AST_CONTROL_SRCUPDATE) {
+				break;
+			default:
 				*fo = f;
 				*rc = who;
-				res =  AST_BRIDGE_COMPLETE;
+				res = AST_BRIDGE_COMPLETE;
 				break;
 			}
-		}
-		if ((f->frametype == AST_FRAME_VOICE) ||
-			(f->frametype == AST_FRAME_TEXT) ||
-			(f->frametype == AST_FRAME_VIDEO) ||
-			(f->frametype == AST_FRAME_IMAGE) ||
-			(f->frametype == AST_FRAME_DTMF) ||
-			(f->frametype == AST_FRAME_CONTROL && f->subclass.integer != AST_CONTROL_PVT_CAUSE_CODE)) {
+			if (res == AST_BRIDGE_COMPLETE) {
+				break;
+			}
+		} else if (f->frametype == AST_FRAME_VOICE
+			|| f->frametype == AST_FRAME_TEXT
+			|| f->frametype == AST_FRAME_VIDEO
+			|| f->frametype == AST_FRAME_IMAGE) {
+			ast_write(other, f);
+		} else if (f->frametype == AST_FRAME_DTMF) {
 			/* monitored dtmf take out of the bridge.
 			 * check if we monitor the specific source.
 			 */
 			int monitored_source = (who == c0) ? AST_BRIDGE_DTMF_CHANNEL_0 : AST_BRIDGE_DTMF_CHANNEL_1;
-			if (f->frametype == AST_FRAME_DTMF && (flags & monitored_source)) {
+
+			if (flags & monitored_source) {
 				*rc = who;
 				*fo = f;
 				res = AST_BRIDGE_COMPLETE;
 				/* Remove from native mode */
 				break;
 			}
-			/* everything else goes to the other side */
 			ast_write(other, f);
 		}
 		ast_frfree(f);

Modified: team/group/performance/main/astobj2.c
URL: http://svnview.digium.com/svn/asterisk/team/group/performance/main/astobj2.c?view=diff&rev=399764&r1=399763&r2=399764
==============================================================================
--- team/group/performance/main/astobj2.c (original)
+++ team/group/performance/main/astobj2.c Tue Sep 24 18:22:28 2013
@@ -1175,7 +1175,8 @@
 		return NULL;
 	}
 
-	flags |= (OBJ_UNLINK | OBJ_POINTER | OBJ_NODATA);
+	flags &= ~OBJ_SEARCH_MASK;
+	flags |= (OBJ_UNLINK | OBJ_SEARCH_OBJECT | OBJ_NODATA);
 	__ao2_callback_debug(c, flags, ao2_match_by_addr, user_data, tag, file, line, func);
 
 	return NULL;
@@ -1189,7 +1190,8 @@
 		return NULL;
 	}
 
-	flags |= (OBJ_UNLINK | OBJ_POINTER | OBJ_NODATA);
+	flags &= ~OBJ_SEARCH_MASK;
+	flags |= (OBJ_UNLINK | OBJ_SEARCH_OBJECT | OBJ_NODATA);
 	__ao2_callback(c, flags, ao2_match_by_addr, user_data);
 
 	return NULL;
@@ -2132,7 +2134,7 @@
 		return NULL;
 	}
 
-	i = abs(self->hash_fn(obj_new, OBJ_POINTER));
+	i = abs(self->hash_fn(obj_new, OBJ_SEARCH_OBJECT));
 	i %= self->n_buckets;
 
 	if (tag) {
@@ -2177,7 +2179,7 @@
 	if (options & AO2_CONTAINER_ALLOC_OPT_INSERT_BEGIN) {
 		if (sort_fn) {
 			AST_DLLIST_TRAVERSE_BACKWARDS_SAFE_BEGIN(&bucket->list, cur, links) {
-				cmp = sort_fn(cur->common.obj, node->common.obj, OBJ_POINTER);
+				cmp = sort_fn(cur->common.obj, node->common.obj, OBJ_SEARCH_OBJECT);
 				if (cmp > 0) {
 					continue;
 				}
@@ -2209,7 +2211,7 @@
 	} else {
 		if (sort_fn) {
 			AST_DLLIST_TRAVERSE_SAFE_BEGIN(&bucket->list, cur, links) {
-				cmp = sort_fn(cur->common.obj, node->common.obj, OBJ_POINTER);
+				cmp = sort_fn(cur->common.obj, node->common.obj, OBJ_SEARCH_OBJECT);
 				if (cmp < 0) {
 					continue;
 				}
@@ -2309,15 +2311,24 @@
 	 * If lookup by pointer or search key, run the hash and optional
 	 * sort functions.  Otherwise, traverse the whole container.
 	 */
-	if (flags & (OBJ_POINTER | OBJ_KEY)) {
+	switch (flags & OBJ_SEARCH_MASK) {
+	case OBJ_SEARCH_OBJECT:
+	case OBJ_SEARCH_KEY:
 		/* we know hash can handle this case */
-		bucket_cur = abs(self->hash_fn(arg, flags & (OBJ_POINTER | OBJ_KEY)));
+		bucket_cur = abs(self->hash_fn(arg, flags & OBJ_SEARCH_MASK));
 		bucket_cur %= self->n_buckets;
 		state->sort_fn = self->common.sort_fn;
-	} else {
+		break;
+	case OBJ_SEARCH_PARTIAL_KEY:
+		/* scan all buckets for partial key matches */
+		bucket_cur = -1;
+		state->sort_fn = self->common.sort_fn;
+		break;
+	default:
 		/* don't know, let's scan all buckets */
 		bucket_cur = -1;
-		state->sort_fn = (flags & OBJ_PARTIAL_KEY) ? self->common.sort_fn : NULL;
+		state->sort_fn = NULL;
+		break;
 	}
 
 	if (state->descending) {
@@ -2353,8 +2364,7 @@
 
 				if (state->sort_fn) {
 					/* Filter node through the sort_fn */
-					cmp = state->sort_fn(node->common.obj, arg,
-						flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY));
+					cmp = state->sort_fn(node->common.obj, arg, flags & OBJ_SEARCH_MASK);
 					if (cmp > 0) {
 						continue;
 					}
@@ -2379,7 +2389,7 @@
 			/* Was this the starting bucket? */
 			if (bucket_cur == state->bucket_start
 				&& (flags & OBJ_CONTINUE)
-				&& (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY))) {
+				&& (flags & OBJ_SEARCH_MASK) != OBJ_SEARCH_NONE) {
 				/* In case the bucket was empty or none of the nodes matched. */
 				state->sort_fn = NULL;
 			}
@@ -2387,7 +2397,7 @@
 			/* Was this the first container bucket? */
 			if (bucket_cur == 0
 				&& (flags & OBJ_CONTINUE)
-				&& (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY))) {
+				&& (flags & OBJ_SEARCH_MASK) != OBJ_SEARCH_NONE) {
 				/* Move to the end to ensure we check every bucket */
 				bucket_cur = self->n_buckets;
 				state->bucket_last = state->bucket_start + 1;
@@ -2434,8 +2444,7 @@
 
 				if (state->sort_fn) {
 					/* Filter node through the sort_fn */
-					cmp = state->sort_fn(node->common.obj, arg,
-						flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY));
+					cmp = state->sort_fn(node->common.obj, arg, flags & OBJ_SEARCH_MASK);
 					if (cmp < 0) {
 						continue;
 					}
@@ -2460,7 +2469,7 @@
 			/* Was this the starting bucket? */
 			if (bucket_cur == state->bucket_start
 				&& (flags & OBJ_CONTINUE)
-				&& (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY))) {
+				&& (flags & OBJ_SEARCH_MASK) != OBJ_SEARCH_NONE) {
 				/* In case the bucket was empty or none of the nodes matched. */
 				state->sort_fn = NULL;
 			}
@@ -2468,7 +2477,7 @@
 			/* Was this the last container bucket? */
 			if (bucket_cur == self->n_buckets - 1
 				&& (flags & OBJ_CONTINUE)
-				&& (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY))) {
+				&& (flags & OBJ_SEARCH_MASK) != OBJ_SEARCH_NONE) {
 				/* Move to the beginning to ensure we check every bucket */
 				bucket_cur = -1;
 				state->bucket_last = state->bucket_start;
@@ -2541,8 +2550,7 @@
 
 				if (state->sort_fn) {
 					/* Filter node through the sort_fn */
-					cmp = state->sort_fn(node->common.obj, arg,
-						flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY));
+					cmp = state->sort_fn(node->common.obj, arg, flags & OBJ_SEARCH_MASK);
 					if (cmp > 0) {
 						continue;
 					}
@@ -2573,7 +2581,7 @@
 			/* Was this the first container bucket? */
 			if (bucket_cur == 0
 				&& (flags & OBJ_CONTINUE)
-				&& (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY))) {
+				&& (flags & OBJ_SEARCH_MASK) != OBJ_SEARCH_NONE) {
 				/* Move to the end to ensure we check every bucket */
 				bucket_cur = self->n_buckets;
 				state->bucket_last = state->bucket_start + 1;
@@ -2608,8 +2616,7 @@
 
 				if (state->sort_fn) {
 					/* Filter node through the sort_fn */
-					cmp = state->sort_fn(node->common.obj, arg,
-						flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY));
+					cmp = state->sort_fn(node->common.obj, arg, flags & OBJ_SEARCH_MASK);
 					if (cmp < 0) {
 						continue;
 					}
@@ -2640,7 +2647,7 @@
 			/* Was this the last container bucket? */
 			if (bucket_cur == self->n_buckets - 1
 				&& (flags & OBJ_CONTINUE)
-				&& (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY))) {
+				&& (flags & OBJ_SEARCH_MASK) != OBJ_SEARCH_NONE) {
 				/* Move to the beginning to ensure we check every bucket */
 				bucket_cur = -1;
 				state->bucket_last = state->bucket_start;
@@ -3042,7 +3049,7 @@
 			++count_obj;
 
 			/* Check container hash key for expected bucket. */
-			bucket_exp = abs(self->hash_fn(node->common.obj, OBJ_POINTER));
+			bucket_exp = abs(self->hash_fn(node->common.obj, OBJ_SEARCH_OBJECT));
 			bucket_exp %= self->n_buckets;
 			if (bucket != bucket_exp) {
 				ast_log(LOG_ERROR, "Bucket %d node hashes to bucket %d!\n",
@@ -3053,7 +3060,7 @@
 			/* Check sort if configured. */
 			if (self->common.sort_fn) {
 				if (obj_last
-					&& self->common.sort_fn(obj_last, node->common.obj, OBJ_POINTER) > 0) {
+					&& self->common.sort_fn(obj_last, node->common.obj, OBJ_SEARCH_OBJECT) > 0) {
 					ast_log(LOG_ERROR, "Bucket %d nodes out of sorted order!\n",
 						bucket);
 					return -1;
@@ -3509,9 +3516,9 @@
  * \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.
+ *   OBJ_SEARCH_OBJECT - if set, 'obj_right', is an object.
+ *   OBJ_SEARCH_KEY - if set, 'obj_right', is a search key item that is not an object.
+ *   OBJ_SEARCH_PARTIAL_KEY - if set, 'obj_right', is a partial search key item that is not an object.
  * \param bias How to bias search direction for duplicates
  *
  * \return enum empty_node_direction to proceed.
@@ -4259,7 +4266,7 @@
 	for (;;) {
 		if (!cur->common.obj) {
 			/* Which direction do we go to insert this node? */
-			if (rb_find_empty_direction(cur, sort_fn, node->common.obj, OBJ_POINTER, bias)
+			if (rb_find_empty_direction(cur, sort_fn, node->common.obj, OBJ_SEARCH_OBJECT, bias)
 				== GO_LEFT) {
 				if (cur->left) {
 					cur = cur->left;
@@ -4283,7 +4290,7 @@
 			rb_insert_fixup(self, node);
 			return AO2_CONTAINER_INSERT_NODE_INSERTED;
 		}
-		cmp = sort_fn(cur->common.obj, node->common.obj, OBJ_POINTER);
+		cmp = sort_fn(cur->common.obj, node->common.obj, OBJ_SEARCH_OBJECT);
 		if (cmp > 0) {
 			if (cur->left) {
 				cur = cur->left;
@@ -4365,7 +4372,7 @@
 					/* Reject inserting the same object */
 					return AO2_CONTAINER_INSERT_NODE_REJECTED;
 				}
-				cmp = sort_fn(next->common.obj, node->common.obj, OBJ_POINTER);
+				cmp = sort_fn(next->common.obj, node->common.obj, OBJ_SEARCH_OBJECT);
 				if (cmp) {
 					break;
 				}
@@ -4381,7 +4388,7 @@
 					/* Reject inserting the same object */
 					return AO2_CONTAINER_INSERT_NODE_REJECTED;
 				}
-				cmp = sort_fn(next->common.obj, node->common.obj, OBJ_POINTER);
+				cmp = sort_fn(next->common.obj, node->common.obj, OBJ_SEARCH_OBJECT);
 				if (cmp) {
 					break;
 				}
@@ -4406,7 +4413,7 @@
 					/* Reject inserting the same object */
 					return AO2_CONTAINER_INSERT_NODE_REJECTED;
 				}
-				cmp = sort_fn(next->common.obj, node->common.obj, OBJ_POINTER);
+				cmp = sort_fn(next->common.obj, node->common.obj, OBJ_SEARCH_OBJECT);
 				if (cmp) {
 					break;
 				}
@@ -4422,7 +4429,7 @@
 					/* Reject inserting the same object */
 					return AO2_CONTAINER_INSERT_NODE_REJECTED;
 				}
-				cmp = sort_fn(next->common.obj, node->common.obj, OBJ_POINTER);
+				cmp = sort_fn(next->common.obj, node->common.obj, OBJ_SEARCH_OBJECT);
 				if (cmp) {
 					break;
 				}
@@ -4543,8 +4550,7 @@
 
 		if (state->sort_fn) {
 			/* Filter node through the sort_fn */
-			cmp = state->sort_fn(node->common.obj, arg,
-				flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY));
+			cmp = state->sort_fn(node->common.obj, arg, flags & OBJ_SEARCH_MASK);
 			if (cmp) {
 				/* No more nodes in this container are possible to match. */
 				break;
@@ -4580,9 +4586,9 @@
  * \param self Container to operate upon.
  * \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.
+ *   OBJ_SEARCH_OBJECT - if set, 'obj_right', is an object.
+ *   OBJ_SEARCH_KEY - if set, 'obj_right', is a search key item that is not an object.
+ *   OBJ_SEARCH_PARTIAL_KEY - if set, 'obj_right', is a partial search key item that is not an object.
  *   OBJ_CONTINUE - if set, return node right before or right after search key if not a match.
  * \param bias How to bias search direction for duplicates
  *
@@ -4597,7 +4603,7 @@
 	struct rbtree_node *next = NULL;
 	ao2_sort_fn *sort_fn;
 
-	sort_flags = flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY);
+	sort_flags = flags & OBJ_SEARCH_MASK;
 	sort_fn = self->common.sort_fn;
 
 	/* Find node where normal search would find it. */
@@ -4730,12 +4736,17 @@
 	state->arg = arg;
 	state->flags = flags;
 
-	if (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+	switch (flags & OBJ_SEARCH_MASK) {
+	case OBJ_SEARCH_OBJECT:
+	case OBJ_SEARCH_KEY:
+	case OBJ_SEARCH_PARTIAL_KEY:
 		/* We are asked to do a directed search. */
 		state->sort_fn = self->common.sort_fn;
-	} else {
+		break;
+	default:
 		/* Don't know, let's visit all nodes */
 		state->sort_fn = NULL;
+		break;
 	}
 
 	if (!self->root) {
@@ -4763,7 +4774,7 @@
 		switch (self->common.options & AO2_CONTAINER_ALLOC_OPT_DUPS_MASK) {
 		case AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT:
 		case AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE:
-			if ((flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) != OBJ_PARTIAL_KEY) {
+			if ((flags & OBJ_SEARCH_MASK) != OBJ_SEARCH_PARTIAL_KEY) {
 				/* There are no duplicates allowed. */
 				bias = BIAS_EQUAL;
 				break;
@@ -4798,7 +4809,7 @@
 		switch (self->common.options & AO2_CONTAINER_ALLOC_OPT_DUPS_MASK) {
 		case AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT:
 		case AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE:
-			if ((flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) != OBJ_PARTIAL_KEY) {
+			if ((flags & OBJ_SEARCH_MASK) != OBJ_SEARCH_PARTIAL_KEY) {
 				/* There are no duplicates allowed. */
 				bias = BIAS_EQUAL;
 				break;
@@ -5228,7 +5239,7 @@
 			}
 
 			if (obj_last) {
-				if (self->common.sort_fn(obj_last, node->common.obj, OBJ_POINTER) > 0) {
+				if (self->common.sort_fn(obj_last, node->common.obj, OBJ_SEARCH_OBJECT) > 0) {
 					ast_log(LOG_ERROR, "Tree nodes are out of sorted order!\n");
 					return -1;
 				}
@@ -5517,18 +5528,33 @@
 	const struct ao2_reg_container *reg_left = obj_left;
 	int cmp;
 
-	if (flags & OBJ_KEY) {
-		const char *name = obj_right;
-
-		cmp = strcasecmp(reg_left->name, name);
-	} else if (flags & OBJ_PARTIAL_KEY) {
-		const struct ao2_reg_partial_key *partial_key = obj_right;
-
-		cmp = strncasecmp(reg_left->name, partial_key->name, partial_key->len);
-	} else {
-		const struct ao2_reg_container *reg_right = obj_right;
-
-		cmp = strcasecmp(reg_left->name, reg_right->name);
+	switch (flags & OBJ_SEARCH_MASK) {
+	case OBJ_SEARCH_OBJECT:
+		{
+			const struct ao2_reg_container *reg_right = obj_right;
+
+			cmp = strcasecmp(reg_left->name, reg_right->name);
+		}
+		break;
+	case OBJ_SEARCH_KEY:
+		{
+			const char *name = obj_right;
+
+			cmp = strcasecmp(reg_left->name, name);
+		}
+		break;
+	case OBJ_SEARCH_PARTIAL_KEY:
+		{
+			const struct ao2_reg_partial_key *partial_key = obj_right;
+
+			cmp = strncasecmp(reg_left->name, partial_key->name, partial_key->len);
+		}
+		break;
+	default:
+		/* Sort can only work on something with a full or partial key. */
+		ast_assert(0);
+		cmp = 0;
+		break;
 	}
 	return cmp;
 }
@@ -5575,7 +5601,7 @@
 void ao2_container_unregister(const char *name)
 {
 #if defined(AST_DEVMODE)
-	ao2_t_find(reg_containers, name, OBJ_UNLINK | OBJ_NODATA | OBJ_KEY,
+	ao2_t_find(reg_containers, name, OBJ_UNLINK | OBJ_NODATA | OBJ_SEARCH_KEY,
 		"Unregister container");
 #endif	/* defined(AST_DEVMODE) */
 }
@@ -5606,7 +5632,7 @@
 	partial_key.name = a->word;
 	which.find_nth = a->n;
 	which.count = 0;
-	reg = ao2_t_callback_data(reg_containers, partial_key.len ? OBJ_PARTIAL_KEY : 0,
+	reg = ao2_t_callback_data(reg_containers, partial_key.len ? OBJ_SEARCH_PARTIAL_KEY : 0,
 		ao2_complete_reg_cb, &partial_key, &which, "Find partial registered container");
 	if (reg) {
 		name = ast_strdup(reg->name);
@@ -5675,7 +5701,7 @@
 	}
 
 	name = a->argv[3];
-	reg = ao2_t_find(reg_containers, name, OBJ_KEY, "Find registered container");
+	reg = ao2_t_find(reg_containers, name, OBJ_SEARCH_KEY, "Find registered container");
 	if (reg) {
 		ao2_container_dump(reg->registered, 0, name, (void *) &a->fd, cli_output,
 			reg->prnt_obj);
@@ -5711,7 +5737,7 @@
 	}
 
 	name = a->argv[3];
-	reg = ao2_t_find(reg_containers, name, OBJ_KEY, "Find registered container");
+	reg = ao2_t_find(reg_containers, name, OBJ_SEARCH_KEY, "Find registered container");
 	if (reg) {
 		ao2_container_stats(reg->registered, 0, name, (void *) &a->fd, cli_output);
 		ao2_t_ref(reg, -1, "Done with registered container object.");
@@ -5746,7 +5772,7 @@
 	}
 
 	name = a->argv[3];
-	reg = ao2_t_find(reg_containers, name, OBJ_KEY, "Find registered container");
+	reg = ao2_t_find(reg_containers, name, OBJ_SEARCH_KEY, "Find registered container");
 	if (reg) {
 		ast_cli(a->fd, "Container check of '%s': %s.\n", name,
 			ao2_container_check(reg->registered, 0) ? "failed" : "OK");

Modified: team/group/performance/main/cdr.c
URL: http://svnview.digium.com/svn/asterisk/team/group/performance/main/cdr.c?view=diff&rev=399764&r1=399763&r2=399764
==============================================================================
--- team/group/performance/main/cdr.c (original)
+++ team/group/performance/main/cdr.c Tue Sep 24 18:22:28 2013
@@ -327,7 +327,7 @@
 AST_MUTEX_DEFINE_STATIC(cdr_pending_lock);
 static ast_cond_t cdr_pending_cond;
 
-/*! \brief A container of the active CDRs indexed by Party A channel name */
+/*! \brief A container of the active CDRs indexed by Party A channel id */
 static struct ao2_container *active_cdrs_by_channel;
 
 /*! \brief Message router for stasis messages regarding channel state */
@@ -682,6 +682,7 @@
 	struct ast_flags flags;                 /*!< Flags on the CDR */
 	AST_DECLARE_STRING_FIELDS(
 		AST_STRING_FIELD(linkedid);         /*!< Linked ID. Cached here as it may change out from party A, which must be immutable */
+		AST_STRING_FIELD(uniqueid);			/*!< Unique id of party A. Cached here as it is the primary key of this CDR */
 		AST_STRING_FIELD(name);             /*!< Channel name of party A. Cached here as the party A address may change */
 		AST_STRING_FIELD(bridge);           /*!< The bridge the party A happens to be in. */
 		AST_STRING_FIELD(appl);             /*!< The last accepted application party A was in */
@@ -772,42 +773,58 @@
 	}
 }
 /*! \internal
- * \brief Hash function for containers of CDRs indexing by Party A name */
+ * \brief Hash function for containers of CDRs indexing by Party A uniqueid */
 static int cdr_object_channel_hash_fn(const void *obj, const int flags)
 {
-	const struct cdr_object *cdr = obj;
-	const char *name = (flags & OBJ_KEY) ? obj : cdr->name;
-	return ast_str_case_hash(name);
+	const struct cdr_object *cdr;
+	const char *key;
+
+	switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+	case OBJ_KEY:
+		key = obj;
+		break;
+	case OBJ_POINTER:
+		cdr = obj;
+		key = cdr->uniqueid;
+		break;
+	default:
+		ast_assert(0);
+		return 0;
+	}
+	return ast_str_case_hash(key);
 }
 
 /*! \internal
- * \brief Comparison function for containers of CDRs indexing by Party A name
+ * \brief Comparison function for containers of CDRs indexing by Party A uniqueid
  */
 static int cdr_object_channel_cmp_fn(void *obj, void *arg, int flags)
 {
-	struct cdr_object *left = obj;
-	struct cdr_object *right = arg;
-	const char *match = (flags & OBJ_KEY) ? arg : right->name;
-	return strcasecmp(left->name, match) ? 0 : (CMP_MATCH | CMP_STOP);
-}
-
-/*! \internal
- * \brief Comparison function for containers of CDRs indexing by bridge. Note
- * that we expect there to be collisions, as a single bridge may have multiple
- * CDRs active at one point in time
- */
-static int cdr_object_bridge_cmp_fn(void *obj, void *arg, int flags)
-{
-	struct cdr_object *left = obj;
-	struct cdr_object *it_cdr;
-	const char *match = arg;
-
-	for (it_cdr = left; it_cdr; it_cdr = it_cdr->next) {
-		if (!strcasecmp(it_cdr->bridge, match)) {
-			return CMP_MATCH;
-		}
-	}
-	return 0;
+    struct cdr_object *left = obj;
+    struct cdr_object *right = arg;
+    const char *right_key = arg;
+    int cmp;
+
+    switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+    case OBJ_POINTER:
+        right_key = right->uniqueid;
+        /* Fall through */
+    case OBJ_KEY:
+        cmp = strcmp(left->uniqueid, right_key);
+        break;
+    case OBJ_PARTIAL_KEY:
+        /*
+         * We could also use a partial key struct containing a length
+         * so strlen() does not get called for every comparison instead.
+         */
+        cmp = strncmp(left->uniqueid, right_key, strlen(right_key));
+        break;
+    default:
+        /* Sort can only work on something with a full or partial key. */
+        ast_assert(0);
+        cmp = 0;
+        break;
+    }
+    return cmp ? 0 : CMP_MATCH;
 }
 
 /*!
@@ -854,6 +871,7 @@
 		ao2_cleanup(cdr);
 		return NULL;
 	}
+	ast_string_field_set(cdr, uniqueid, chan->uniqueid);
 	ast_string_field_set(cdr, name, chan->name);
 	ast_string_field_set(cdr, linkedid, chan->linkedid);
 	cdr->disposition = AST_CDR_NULL;
@@ -985,7 +1003,7 @@
 	} else if (left->snapshot->creationtime.tv_sec > right->snapshot->creationtime.tv_sec) {
 		return right;
 	} else if (left->snapshot->creationtime.tv_usec > right->snapshot->creationtime.tv_usec) {
-			return right;
+		return right;
 	} else {
 		/* Okay, fine, take the left one */
 		return left;
@@ -1062,12 +1080,15 @@
 	struct ast_var_t *it_var, *it_copy_var;
 	struct ast_channel_snapshot *party_a;
 	struct ast_channel_snapshot *party_b;
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
 
 	for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
 		struct ast_cdr *cdr_copy;
 
 		/* Don't create records for CDRs where the party A was a dialed channel */
-		if (snapshot_is_dialed(it_cdr->party_a.snapshot)) {
+		if (snapshot_is_dialed(it_cdr->party_a.snapshot) && !it_cdr->party_b.snapshot) {
+			CDR_DEBUG(mod_cfg, "%p - %s is dialed and has no Party B; discarding\n", it_cdr,
+				it_cdr->party_a.snapshot->name);
 			continue;
 		}
 
@@ -1437,6 +1458,7 @@
 static int single_state_bridge_enter_comparison(struct cdr_object *cdr,
 		struct cdr_object *cand_cdr)
 {
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
 	struct cdr_object_snapshot *party_a;
 
 	/* Don't match on ourselves */
@@ -1447,6 +1469,8 @@
 	/* Try the candidate CDR's Party A first */
 	party_a = cdr_object_pick_party_a(&cdr->party_a, &cand_cdr->party_a);
 	if (!strcasecmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
+		CDR_DEBUG(mod_cfg, "%p - Party A %s has new Party B %s\n",
+			cdr, cdr->party_a.snapshot->name, cand_cdr->party_a.snapshot->name);
 		cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_a);
 		if (!cand_cdr->party_b.snapshot) {
 			/* We just stole them - finalize their CDR. Note that this won't
@@ -1465,6 +1489,8 @@
 	}
 	party_a = cdr_object_pick_party_a(&cdr->party_a, &cand_cdr->party_b);
 	if (!strcasecmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
+		CDR_DEBUG(mod_cfg, "%p - Party A %s has new Party B %s\n",
+			cdr, cdr->party_a.snapshot->name, cand_cdr->party_b.snapshot->name);
 		cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_b);
 		return 0;
 	}
@@ -1474,27 +1500,31 @@
 
 static enum process_bridge_enter_results single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
 {
-	struct ao2_iterator *it_cdrs;
-	struct cdr_object *cand_cdr_master;
-	char *bridge_id = ast_strdupa(bridge->uniqueid);
+	struct ao2_iterator it_cdrs;
+	char *channel_id;
 	int success = 0;
 
 	ast_string_field_set(cdr, bridge, bridge->uniqueid);
 
-	/* Get parties in the bridge */
-	it_cdrs = ao2_callback(active_cdrs_by_channel, OBJ_MULTIPLE,
-			cdr_object_bridge_cmp_fn, bridge_id);
-	if (!it_cdrs) {
-		/* No one in the bridge yet! */
+	if (ao2_container_count(bridge->channels) == 1) {
+		/* No one in the bridge yet but us! */
 		cdr_object_transition_state(cdr, &bridge_state_fn_table);
 		return BRIDGE_ENTER_ONLY_PARTY;
 	}
 
-	while ((cand_cdr_master = ao2_iterator_next(it_cdrs))) {
+	for (it_cdrs = ao2_iterator_init(bridge->channels, 0);
+		!success && (channel_id = ao2_iterator_next(&it_cdrs));
+		ao2_ref(channel_id, -1)) {
+		RAII_VAR(struct cdr_object *, cand_cdr_master,
+			ao2_find(active_cdrs_by_channel, channel_id, OBJ_KEY),
+			ao2_cleanup);
 		struct cdr_object *cand_cdr;
-		RAII_VAR(struct cdr_object *, cdr_cleanup, cand_cdr_master, ao2_cleanup);
-		SCOPED_AO2LOCK(lock, cand_cdr_master);
-
+
+		if (!cand_cdr_master) {
+			continue;
+		}
+
+		ao2_lock(cand_cdr_master);
 		for (cand_cdr = cand_cdr_master; cand_cdr; cand_cdr = cand_cdr->next) {
 			/* Skip any records that are not in a bridge or in this bridge.
 			 * I'm not sure how that would happen, but it pays to be careful. */
@@ -1510,8 +1540,9 @@
 			success = 1;
 			break;
 		}
-	}
-	ao2_iterator_destroy(it_cdrs);
+		ao2_unlock(cand_cdr_master);
+	}
+	ao2_iterator_destroy(&it_cdrs);
 
 	/* We always transition state, even if we didn't get a peer */
 	cdr_object_transition_state(cdr, &bridge_state_fn_table);
@@ -1620,27 +1651,32 @@
 
 static enum process_bridge_enter_results dial_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
 {
-	struct ao2_iterator *it_cdrs;
-	char *bridge_id = ast_strdupa(bridge->uniqueid);
-	struct cdr_object *cand_cdr_master;
+	struct ao2_iterator it_cdrs;
+	char *channel_id;
 	int success = 0;
 
 	ast_string_field_set(cdr, bridge, bridge->uniqueid);
 
 	/* Get parties in the bridge */
-	it_cdrs = ao2_callback(active_cdrs_by_channel, OBJ_MULTIPLE,
-			cdr_object_bridge_cmp_fn, bridge_id);
-	if (!it_cdrs) {
-		/* No one in the bridge yet! */
+	if (ao2_container_count(bridge->channels) == 1) {
+		/* No one in the bridge yet but us! */
 		cdr_object_transition_state(cdr, &bridge_state_fn_table);
 		return BRIDGE_ENTER_ONLY_PARTY;
 	}
 
-	while ((cand_cdr_master = ao2_iterator_next(it_cdrs))) {
+	for (it_cdrs = ao2_iterator_init(bridge->channels, 0);
+		!success && (channel_id = ao2_iterator_next(&it_cdrs));
+		ao2_ref(channel_id, -1)) {
+		RAII_VAR(struct cdr_object *, cand_cdr_master,
+			ao2_find(active_cdrs_by_channel, channel_id, OBJ_KEY),
+			ao2_cleanup);
 		struct cdr_object *cand_cdr;
-		RAII_VAR(struct cdr_object *, cdr_cleanup, cand_cdr_master, ao2_cleanup);
-		SCOPED_AO2LOCK(lock, cand_cdr_master);
-
+
+		if (!cand_cdr_master) {
+			continue;
+		}
+
+		ao2_lock(cand_cdr_master);
 		for (cand_cdr = cand_cdr_master; cand_cdr; cand_cdr = cand_cdr->next) {
 			/* Skip any records that are not in a bridge or in this bridge.
 			 * I'm not sure how that would happen, but it pays to be careful. */
@@ -1669,8 +1705,9 @@
 			success = 1;
 			break;
 		}
-	}
-	ao2_iterator_destroy(it_cdrs);
+		ao2_unlock(cand_cdr_master);
+	}
+	ao2_iterator_destroy(&it_cdrs);
 
 	/* We always transition state, even if we didn't get a peer */
 	cdr_object_transition_state(cdr, &bridge_state_fn_table);
@@ -1829,9 +1866,9 @@
 
 	/* Figure out who is running this show */
 	if (caller) {
-		cdr = ao2_find(active_cdrs_by_channel, caller->name, OBJ_KEY);
+		cdr = ao2_find(active_cdrs_by_channel, caller->uniqueid, OBJ_KEY);
 	} else {
-		cdr = ao2_find(active_cdrs_by_channel, peer->name, OBJ_KEY);
+		cdr = ao2_find(active_cdrs_by_channel, peer->uniqueid, OBJ_KEY);
 	}
 
 	if (!cdr) {
@@ -1986,6 +2023,7 @@
 	struct stasis_cache_update *update = stasis_message_data(message);
 	struct ast_channel_snapshot *old_snapshot;
 	struct ast_channel_snapshot *new_snapshot;
+	const char *uniqueid;
 	const char *name;
 	struct cdr_object *it_cdr;
 
@@ -1994,16 +2032,12 @@
 
 	old_snapshot = stasis_message_data(update->old_snapshot);
 	new_snapshot = stasis_message_data(update->new_snapshot);
+	uniqueid = new_snapshot ? new_snapshot->uniqueid : old_snapshot->uniqueid;
 	name = new_snapshot ? new_snapshot->name : old_snapshot->name;
 
 	if (filter_channel_cache_message(old_snapshot, new_snapshot)) {
 		return;
 	}
-
-	CDR_DEBUG(mod_cfg, "Channel Update message for %s: %u.%08u\n",
-			name,
-			(unsigned int)stasis_message_timestamp(message)->tv_sec,
-			(unsigned int)stasis_message_timestamp(message)->tv_usec);
 
 	if (new_snapshot && !old_snapshot) {
 		cdr = cdr_object_alloc(new_snapshot);
@@ -2015,7 +2049,7 @@
 
 	/* Handle Party A */
 	if (!cdr) {
-		cdr = ao2_find(active_cdrs_by_channel, name, OBJ_KEY);
+		cdr = ao2_find(active_cdrs_by_channel, uniqueid, OBJ_KEY);
 	}
 	if (!cdr) {
 		ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", name);
@@ -2027,7 +2061,6 @@
 				if (!it_cdr->fn_table->process_party_a) {
 					continue;
 				}
-				CDR_DEBUG(mod_cfg, "%p - Processing new channel snapshot %s\n", it_cdr, new_snapshot->name);
 				all_reject &= it_cdr->fn_table->process_party_a(it_cdr, new_snapshot);
 			}
 			if (all_reject && check_new_cdr_needed(old_snapshot, new_snapshot)) {
@@ -2121,7 +2154,7 @@
 	RAII_VAR(struct module_config *, mod_cfg,
 			ao2_global_obj_ref(module_configs), ao2_cleanup);
 	RAII_VAR(struct cdr_object *, cdr,
-			ao2_find(active_cdrs_by_channel, channel->name, OBJ_KEY),
+			ao2_find(active_cdrs_by_channel, channel->uniqueid, OBJ_KEY),
 			ao2_cleanup);
 	struct cdr_object *it_cdr;
 	struct bridge_leave_data leave_data = {
@@ -2171,163 +2204,6 @@
 	}
 }
 
-struct bridge_candidate {
-	struct cdr_object *cdr;					/*!< The actual CDR this candidate belongs to, either as A or B */
-	struct cdr_object_snapshot candidate;	/*!< The candidate for a new pairing */
-};
-
-/*! \internal
- * \brief Comparison function for \ref bridge_candidate objects
- */
-static int bridge_candidate_cmp_fn(void *obj, void *arg, int flags)
-{
-	struct bridge_candidate *left = obj;
-	struct bridge_candidate *right = arg;
-	const char *match = (flags & OBJ_KEY) ? arg : right->candidate.snapshot->name;
-	return strcasecmp(left->candidate.snapshot->name, match) ? 0 : (CMP_MATCH | CMP_STOP);
-}
-
-/*! \internal
- * \brief Hash function for \ref bridge_candidate objects
- */
-static int bridge_candidate_hash_fn(const void *obj, const int flags)
-{
-	const struct bridge_candidate *bc = obj;
-	const char *id = (flags & OBJ_KEY) ? obj : bc->candidate.snapshot->name;
-	return ast_str_case_hash(id);
-}
-
-/*! \brief \ref bridge_candidate Destructor */
-static void bridge_candidate_dtor(void *obj)
-{
-	struct bridge_candidate *bcand = obj;
-	ao2_cleanup(bcand->cdr);
-	ao2_cleanup(bcand->candidate.snapshot);
-	free_variables(&bcand->candidate.variables);
-}
-
-/*!
- * \brief \ref bridge_candidate Constructor
- * \param cdr The \ref cdr_object that is a candidate for being compared to in
- *  a bridge operation
- * \param candidate The \ref cdr_object_snapshot candidate snapshot in the CDR
- *  that should be used during the operaton
- */
-static struct bridge_candidate *bridge_candidate_alloc(struct cdr_object *cdr, struct cdr_object_snapshot *candidate)
-{
-	struct bridge_candidate *bcand;
-
-	bcand = ao2_alloc(sizeof(*bcand), bridge_candidate_dtor);
-	if (!bcand) {
-		return NULL;
-	}
-	bcand->cdr = cdr;
-	ao2_ref(bcand->cdr, +1);
-	bcand->candidate.flags = candidate->flags;
-	strcpy(bcand->candidate.userfield, candidate->userfield);
-	bcand->candidate.snapshot = candidate->snapshot;
-	ao2_ref(bcand->candidate.snapshot, +1);
-	copy_variables(&bcand->candidate.variables, &candidate->variables);
-

[... 562 lines stripped ...]



More information about the asterisk-commits mailing list