[asterisk-commits] rmudgett: branch group/bridge_construction r386303 - in /team/group/bridge_co...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Mon Apr 22 12:35:18 CDT 2013


Author: rmudgett
Date: Mon Apr 22 12:35:14 2013
New Revision: 386303

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=386303
Log:
Get local channel optimization working again using the new bridging model.

The primary method of optimization is to merge bridges together.  The
optimizing local channels continuously check for empty queues in the
local;1 and local;2 channels.  Once it is found that the queues are empty
and the bridges allow merging, either BridgeA or BridgeB is merged into
the other bridge.

A future additional way to optimize out local channels in more
circumstances is to swap the Local;2 channel in BridgeB with the only peer
channel (A) in BridgeA.  This optimization strategy would be needed if
BridgeB prohibits bridge merges.

A -- BridgeA -- Local;1-Local;2 -- BridgeB -- B

(closes issue ASTERISK-21058)
Reported by: Matt Jordan

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

Modified:
    team/group/bridge_construction/bridges/bridge_builtin_features.c
    team/group/bridge_construction/channels/chan_local.c
    team/group/bridge_construction/include/asterisk/bridging.h
    team/group/bridge_construction/main/bridging.c

Modified: team/group/bridge_construction/bridges/bridge_builtin_features.c
URL: http://svnview.digium.com/svn/asterisk/team/group/bridge_construction/bridges/bridge_builtin_features.c?view=diff&rev=386303&r1=386302&r2=386303
==============================================================================
--- team/group/bridge_construction/bridges/bridge_builtin_features.c (original)
+++ team/group/bridge_construction/bridges/bridge_builtin_features.c Mon Apr 22 12:35:14 2013
@@ -193,7 +193,7 @@
 	}
 
 	/* Impart the new channel onto the bridge, and have it take our place. */
-	if (ast_bridge_impart(bridge, chan, bridge_channel->chan, NULL, 1)) {
+	if (ast_bridge_impart(bridge_channel->bridge, chan, bridge_channel->chan, NULL, 1)) {
 		ast_hangup(chan);
 		return 0;
 	}
@@ -255,6 +255,8 @@
 	const char *context;
 	enum atxfer_code transfer_code = ATXFER_INCOMPLETE;
 
+	bridge = ast_bridge_channel_merge_inhibit(bridge_channel, +1);
+
 /* BUGBUG the peer needs to be put on hold for the transfer. */
 	ast_channel_lock(bridge_channel->chan);
 	context = ast_strdupa(get_transfer_context(bridge_channel->chan,
@@ -263,12 +265,16 @@
 
 	/* Grab the extension to transfer to */
 	if (grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) {
+		ast_bridge_merge_inhibit(bridge, -1);
+		ao2_ref(bridge, -1);
 		return 0;
 	}
 
 	/* Get a channel that is the destination we wish to call */
 	peer = dial_transfer(bridge_channel->chan, exten, context);
 	if (!peer) {
+		ast_bridge_merge_inhibit(bridge, -1);
+		ao2_ref(bridge, -1);
 /* BUGBUG beeperr needs to be configurable from features.conf */
 		ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
 		return 0;
@@ -294,6 +300,8 @@
 			attended_transfer_threeway, &transfer_code, NULL, 0)) {
 		ast_bridge_features_cleanup(&caller_features);
 		ast_hangup(peer);
+		ast_bridge_merge_inhibit(bridge, -1);
+		ao2_ref(bridge, -1);
 /* BUGBUG beeperr needs to be configurable from features.conf */
 		ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
 		return 0;
@@ -306,10 +314,13 @@
 	if (!attended_bridge) {
 		ast_bridge_features_cleanup(&caller_features);
 		ast_hangup(peer);
+		ast_bridge_merge_inhibit(bridge, -1);
+		ao2_ref(bridge, -1);
 /* BUGBUG beeperr needs to be configurable from features.conf */
 		ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
 		return 0;
 	}
+	ast_bridge_merge_inhibit(attended_bridge, +1);
 
 	/* This is how this is going down, we are imparting the channel we called above into this bridge first */
 /* BUGBUG we should impart the peer as an independent and move it to the original bridge. */
@@ -317,6 +328,8 @@
 		ast_bridge_destroy(attended_bridge);
 		ast_bridge_features_cleanup(&caller_features);
 		ast_hangup(peer);
+		ast_bridge_merge_inhibit(bridge, -1);
+		ao2_ref(bridge, -1);
 /* BUGBUG beeperr needs to be configurable from features.conf */
 		ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
 		return 0;
@@ -361,7 +374,7 @@
 	case ATXFER_COMPLETE:
 		/* The peer takes our place in the bridge. */
 		ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
-		xfer_failed = ast_bridge_impart(bridge, peer, bridge_channel->chan, NULL, 1);
+		xfer_failed = ast_bridge_impart(bridge_channel->bridge, peer, bridge_channel->chan, NULL, 1);
 		break;
 	case ATXFER_THREEWAY:
 		/*
@@ -370,12 +383,14 @@
 		 * Just impart the peer onto the bridge and have us return to it
 		 * as normal.
 		 */
-		xfer_failed = ast_bridge_impart(bridge, peer, NULL, NULL, 1);
+		xfer_failed = ast_bridge_impart(bridge_channel->bridge, peer, NULL, NULL, 1);
 		break;
 	case ATXFER_ABORT:
 		/* Transferer decided not to transfer the call after all. */
 		break;
 	}
+	ast_bridge_merge_inhibit(bridge, -1);
+	ao2_ref(bridge, -1);
 	if (xfer_failed) {
 		ast_hangup(peer);
 		if (!ast_check_hangup_locked(bridge_channel->chan)) {

Modified: team/group/bridge_construction/channels/chan_local.c
URL: http://svnview.digium.com/svn/asterisk/team/group/bridge_construction/channels/chan_local.c?view=diff&rev=386303&r1=386302&r2=386303
==============================================================================
--- team/group/bridge_construction/channels/chan_local.c (original)
+++ team/group/bridge_construction/channels/chan_local.c Mon Apr 22 12:35:14 2013
@@ -54,6 +54,7 @@
 #include "asterisk/stringfields.h"
 #include "asterisk/devicestate.h"
 #include "asterisk/astobj2.h"
+#include "asterisk/bridging.h"
 
 /*** DOCUMENTATION
 	<manager name="LocalOptimizeAway" language="en_US">
@@ -151,10 +152,9 @@
 	struct ast_channel *chan;       /*!< Outbound channel - PBX is run here */
 };
 
-#define LOCAL_ALREADY_MASQED  (1 << 0) /*!< Already masqueraded */
-#define LOCAL_LAUNCHED_PBX    (1 << 1) /*!< PBX was launched */
-#define LOCAL_NO_OPTIMIZATION (1 << 2) /*!< Do not optimize using masquerading */
-#define LOCAL_MOH_PASSTHRU    (1 << 3) /*!< Pass through music on hold start/stop frames */
+#define LOCAL_LAUNCHED_PBX    (1 << 0) /*!< PBX was launched */
+#define LOCAL_NO_OPTIMIZATION (1 << 1) /*!< Do not optimize using masquerading */
+#define LOCAL_MOH_PASSTHRU    (1 << 2) /*!< Pass through music on hold start/stop frames */
 
 /*!
  * \brief Send a pvt in with no locks held and get all locks
@@ -449,146 +449,32 @@
 
 /*!
  * \internal
- * \note This function assumes that we're only called from the "outbound" local channel side
+ * \brief Check and optimize out the local channels between bridges.
+ * \since 12.0.0
  *
- * \note it is assummed p is locked and reffed before entering this function
+ * \param ast Channel writing a frame into the local channels.
+ * \param p Local channel private.
+ *
+ * \note It is assumed that ast is locked.
+ * \note It is assumed that p is locked.
+ *
+ * \retval 0 if local channels were not optimized out.
+ * \retval non-zero if local channels were optimized out.
  */
-static void check_bridge(struct ast_channel *ast, struct local_pvt *p)
-{
-	struct ast_channel *owner;
-	struct ast_channel *chan;
-	struct ast_channel *bridged_chan;
-	struct ast_frame *f;
-
+static int got_optimized_out(struct ast_channel *ast, struct local_pvt *p)
+{
 	/* Do a few conditional checks early on just to see if this optimization is possible */
-	if (ast_test_flag(p, LOCAL_NO_OPTIMIZATION | LOCAL_ALREADY_MASQED)
-		|| !p->chan || !p->owner) {
-		return;
-	}
-
-	/* Safely get the channel bridged to p->chan */
-	chan = ast_channel_ref(p->chan);
-
-	ao2_unlock(p); /* don't call bridged channel with the pvt locked */
-	bridged_chan = ast_bridged_channel(chan);
-	ao2_lock(p);
-
-	chan = ast_channel_unref(chan);
-
-	/* since we had to unlock p to get the bridged chan, validate our
-	 * data once again and verify the bridged channel is what we expect
-	 * it to be in order to perform this optimization */
-	if (ast_test_flag(p, LOCAL_NO_OPTIMIZATION | LOCAL_ALREADY_MASQED)
-		|| !p->chan || !p->owner
-		|| (ast_channel_internal_bridged_channel(p->chan) != bridged_chan)) {
-		return;
-	}
-
-	/* only do the masquerade if we are being called on the outbound channel,
-	   if it has been bridged to another channel and if there are no pending
-	   frames on the owner channel (because they would be transferred to the
-	   outbound channel during the masquerade)
-	*/
-	if (!ast_channel_internal_bridged_channel(p->chan) /* Not ast_bridged_channel!  Only go one step! */
-		|| !AST_LIST_EMPTY(ast_channel_readq(p->owner))
-		|| ast != p->chan /* Sanity check (should always be false) */) {
-		return;
-	}
-
-	/* Masquerade bridged channel into owner */
-	/* Lock everything we need, one by one, and give up if
-	   we can't get everything.  Remember, we'll get another
-	   chance in just a little bit */
-	if (ast_channel_trylock(ast_channel_internal_bridged_channel(p->chan))) {
-		return;
-	}
-	if (ast_check_hangup(ast_channel_internal_bridged_channel(p->chan))
-		|| ast_channel_trylock(p->owner)) {
-		ast_channel_unlock(ast_channel_internal_bridged_channel(p->chan));
-		return;
-	}
-
-	/*
-	 * At this point we have 4 locks:
-	 * p, p->chan (same as ast), p->chan->_bridge, p->owner
-	 *
-	 * Flush a voice or video frame on the outbound channel to make
-	 * the queue empty faster so we can get optimized out.
-	 */
-	f = AST_LIST_FIRST(ast_channel_readq(p->chan));
-	if (f && (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO)) {
-		AST_LIST_REMOVE_HEAD(ast_channel_readq(p->chan), frame_list);
-		ast_frfree(f);
-		f = AST_LIST_FIRST(ast_channel_readq(p->chan));
-	}
-
-	if (f
-		|| ast_check_hangup(p->owner)
-		|| ast_channel_masquerade(p->owner, ast_channel_internal_bridged_channel(p->chan))) {
-		ast_channel_unlock(p->owner);
-		ast_channel_unlock(ast_channel_internal_bridged_channel(p->chan));
-		return;
-	}
-
-	/* Masquerade got setup. */
-	ast_debug(4, "Masquerading %s <- %s\n",
-		ast_channel_name(p->owner),
-		ast_channel_name(ast_channel_internal_bridged_channel(p->chan)));
-	if (ast_channel_monitor(p->owner)
-		&& !ast_channel_monitor(ast_channel_internal_bridged_channel(p->chan))) {
-		struct ast_channel_monitor *tmp;
-
-		/* If a local channel is being monitored, we don't want a masquerade
-		 * to cause the monitor to go away. Since the masquerade swaps the monitors,
-		 * pre-swapping the monitors before the masquerade will ensure that the monitor
-		 * ends up where it is expected.
-		 */
-		tmp = ast_channel_monitor(p->owner);
-		ast_channel_monitor_set(p->owner, ast_channel_monitor(ast_channel_internal_bridged_channel(p->chan)));
-		ast_channel_monitor_set(ast_channel_internal_bridged_channel(p->chan), tmp);
-	}
-	if (ast_channel_audiohooks(p->chan)) {
-		struct ast_audiohook_list *audiohooks_swapper;
-
-		audiohooks_swapper = ast_channel_audiohooks(p->chan);
-		ast_channel_audiohooks_set(p->chan, ast_channel_audiohooks(p->owner));
-		ast_channel_audiohooks_set(p->owner, audiohooks_swapper);
-	}
-
-	/* If any Caller ID was set, preserve it after masquerade like above. We must check
-	 * to see if Caller ID was set because otherwise we'll mistakingly copy info not
-	 * set from the dialplan and will overwrite the real channel Caller ID. The reason
-	 * for this whole preswapping action is because the Caller ID is set on the channel
-	 * thread (which is the to be masqueraded away local channel) before both local
-	 * channels are optimized away.
-	 */
-	if (ast_channel_caller(p->owner)->id.name.valid || ast_channel_caller(p->owner)->id.number.valid
-		|| ast_channel_caller(p->owner)->id.subaddress.valid || ast_channel_caller(p->owner)->ani.name.valid
-		|| ast_channel_caller(p->owner)->ani.number.valid || ast_channel_caller(p->owner)->ani.subaddress.valid) {
-		SWAP(*ast_channel_caller(p->owner), *ast_channel_caller(ast_channel_internal_bridged_channel(p->chan)));
-	}
-	if (ast_channel_redirecting(p->owner)->from.name.valid || ast_channel_redirecting(p->owner)->from.number.valid
-		|| ast_channel_redirecting(p->owner)->from.subaddress.valid || ast_channel_redirecting(p->owner)->to.name.valid
-		|| ast_channel_redirecting(p->owner)->to.number.valid || ast_channel_redirecting(p->owner)->to.subaddress.valid) {
-		SWAP(*ast_channel_redirecting(p->owner), *ast_channel_redirecting(ast_channel_internal_bridged_channel(p->chan)));
-	}
-	if (ast_channel_dialed(p->owner)->number.str || ast_channel_dialed(p->owner)->subaddress.valid) {
-		SWAP(*ast_channel_dialed(p->owner), *ast_channel_dialed(ast_channel_internal_bridged_channel(p->chan)));
-	}
-	ast_app_group_update(p->chan, p->owner);
-	ast_set_flag(p, LOCAL_ALREADY_MASQED);
-
-	ast_channel_unlock(p->owner);
-	ast_channel_unlock(ast_channel_internal_bridged_channel(p->chan));
-
-	/* Do the masquerade now. */
-	owner = ast_channel_ref(p->owner);
-	ao2_unlock(p);
-	ast_channel_unlock(ast);
-	ast_do_masquerade(owner);
-	ast_channel_unref(owner);
-	ast_channel_lock(ast);
-	ao2_lock(p);
+	if (ast_test_flag(p, LOCAL_NO_OPTIMIZATION) || !p->chan || !p->owner) {
+		return 0;
+	}
+	if (ast == p->owner) {
+		return ast_bridge_local_optimized_out(p->owner, p->chan);
+	}
+	if (ast == p->chan) {
+		return ast_bridge_local_optimized_out(p->chan, p->owner);
+	}
+	/* ast is not valid to optimize. */
+	return 0;
 }
 
 static struct ast_frame  *local_read(struct ast_channel *ast)
@@ -600,28 +486,24 @@
 {
 	struct local_pvt *p = ast_channel_tech_pvt(ast);
 	int res = -1;
-	int isoutbound;
 
 	if (!p) {
 		return -1;
 	}
 
 	/* Just queue for delivery to the other side */
-	ao2_ref(p, 1); /* ref for local_queue_frame */
+	ao2_ref(p, 1);
 	ao2_lock(p);
-	isoutbound = IS_OUTBOUND(ast, p);
-
-	if (isoutbound
-		&& (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO)) {
-		check_bridge(ast, p);
-	}
-
-	if (!ast_test_flag(p, LOCAL_ALREADY_MASQED)) {
-		res = local_queue_frame(p, isoutbound, f, ast, 1);
-	} else {
-		ast_debug(1, "Not posting to '%s' queue since already masqueraded out\n",
-			ast_channel_name(ast));
-		res = 0;
+	switch (f->frametype) {
+	case AST_FRAME_VOICE:
+	case AST_FRAME_VIDEO:
+		if (got_optimized_out(ast, p)) {
+			break;
+		}
+		/* fall through */
+	default:
+		res = local_queue_frame(p, IS_OUTBOUND(ast, p), f, ast, 1);
+		break;
 	}
 	ao2_unlock(p);
 	ao2_ref(p, -1);

Modified: team/group/bridge_construction/include/asterisk/bridging.h
URL: http://svnview.digium.com/svn/asterisk/team/group/bridge_construction/include/asterisk/bridging.h?view=diff&rev=386303&r1=386302&r2=386303
==============================================================================
--- team/group/bridge_construction/include/asterisk/bridging.h (original)
+++ team/group/bridge_construction/include/asterisk/bridging.h Mon Apr 22 12:35:14 2013
@@ -96,6 +96,15 @@
 	AST_BRIDGE_CHANNEL_STATE_END,
 	/*! Bridged channel was forced out and should be hung up */
 	AST_BRIDGE_CHANNEL_STATE_HANGUP,
+};
+
+enum ast_bridge_channel_thread_state {
+	/*! Bridge channel thread is idle/waiting. */
+	AST_BRIDGE_CHANNEL_THREAD_IDLE,
+	/*! Bridge channel thread is writing a normal/simple frame. */
+	AST_BRIDGE_CHANNEL_THREAD_SIMPLE,
+	/*! Bridge channel thread is processing a frame. */
+	AST_BRIDGE_CHANNEL_THREAD_FRAME,
 };
 
 struct ast_bridge_technology;
@@ -186,6 +195,15 @@
 	int alert_pipe[2];
 	/*! TRUE if the bridge channel thread is waiting on channels (needs to be atomically settable) */
 	int waiting;
+	/*!
+	 * \brief The bridge channel thread activity.
+	 *
+	 * \details Used by local channel optimization to determine if
+	 * the thread is in an acceptable state to optimize.
+	 *
+	 * \note Needs to be atomically settable.
+	 */
+	enum ast_bridge_channel_thread_state activity;
 };
 
 enum ast_bridge_action_type {
@@ -762,6 +780,20 @@
 void ast_bridge_merge_inhibit(struct ast_bridge *bridge, int request);
 
 /*!
+ * \brief Adjust the bridge_channel's bridge merge inhibit request count.
+ * \since 12.0.0
+ *
+ * \param bridge_channel What to operate on.
+ * \param request Inhibit request increment.
+ *     (Positive to add requests.  Negative to remove requests.)
+ *
+ * \note This API call is meant for internal bridging operations.
+ *
+ * \retval bridge adjusted merge inhibit with reference count.
+ */
+struct ast_bridge *ast_bridge_channel_merge_inhibit(struct ast_bridge_channel *bridge_channel, int request);
+
+/*!
  * \brief Suspend a channel temporarily from a bridge
  *
  * \param bridge Bridge to suspend the channel from
@@ -807,6 +839,20 @@
  *       Doing so may result in bad things happening.
  */
 int ast_bridge_unsuspend(struct ast_bridge *bridge, struct ast_channel *chan);
+
+/*!
+ * \brief Check and optimize out the local channels between bridges.
+ * \since 12.0.0
+ *
+ * \param chan Local channel writing a frame into the channel driver.
+ * \param peer Other local channel in the pair.
+ *
+ * \note It is assumed that chan is already locked.
+ *
+ * \retval 0 if local channels were not optimized out.
+ * \retval non-zero if local channels were optimized out.
+ */
+int ast_bridge_local_optimized_out(struct ast_channel *chan, struct ast_channel *peer);
 
 /*!
  * \brief Try locking the bridge_channel.

Modified: team/group/bridge_construction/main/bridging.c
URL: http://svnview.digium.com/svn/asterisk/team/group/bridge_construction/main/bridging.c?view=diff&rev=386303&r1=386302&r2=386303
==============================================================================
--- team/group/bridge_construction/main/bridging.c (original)
+++ team/group/bridge_construction/main/bridging.c Mon Apr 22 12:35:14 2013
@@ -2163,6 +2163,7 @@
 		break;
 	default:
 		/* Write the frame to the channel. */
+		bridge_channel->activity = AST_BRIDGE_CHANNEL_THREAD_SIMPLE;
 		ast_write(bridge_channel->chan, fr);
 		break;
 	}
@@ -2236,6 +2237,9 @@
 		chan = ast_waitfor_nandfds(&bridge_channel->chan, 1,
 			&bridge_channel->alert_pipe[0], 1, NULL, &outfd, &ms);
 		bridge_channel->waiting = 0;
+		ast_bridge_channel_lock(bridge_channel);
+		bridge_channel->activity = AST_BRIDGE_CHANNEL_THREAD_FRAME;
+		ast_bridge_channel_unlock(bridge_channel);
 		if (!bridge_channel->suspended
 			&& bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
 			if (chan) {
@@ -2245,6 +2249,7 @@
 				bridge_channel_handle_write(bridge_channel);
 			}
 		}
+		bridge_channel->activity = AST_BRIDGE_CHANNEL_THREAD_IDLE;
 		return;
 	}
 	ast_bridge_channel_unlock(bridge_channel);
@@ -3071,6 +3076,8 @@
  *
  * \param dst_bridge Destination bridge of merge.
  * \param src_bridge Source bridge of merge.
+ * \param kick_me Array of channels to kick from the bridges.
+ * \param num_kick Number of channels in the kick_me array.
  *
  * \return Nothing
  *
@@ -3084,7 +3091,7 @@
  * this operation is completed.  The caller should explicitly
  * call ast_bridge_destroy().
  */
-static void bridge_merge_do(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge)
+static void bridge_merge_do(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_bridge_channel **kick_me, int num_kick)
 {
 	struct ast_bridge_channel *bridge_channel;
 
@@ -3092,6 +3099,15 @@
 		src_bridge->uniqueid, dst_bridge->uniqueid);
 
 	ast_bridge_publish_merge(dst_bridge, src_bridge);
+
+	if (kick_me) {
+		unsigned int idx;
+
+		for (idx = 0; idx < num_kick; ++idx) {
+			ast_bridge_change_state(kick_me[idx], AST_BRIDGE_CHANNEL_STATE_HANGUP);
+			bridge_channel_pull(kick_me[idx]);
+		}
+	}
 
 	/* Move channels from src_bridge over to dst_bridge */
 	while ((bridge_channel = AST_LIST_FIRST(&src_bridge->channels))) {
@@ -3144,7 +3160,7 @@
 	} else {
 		++dst_bridge->inhibit_merge;
 		++src_bridge->inhibit_merge;
-		bridge_merge_do(dst_bridge, src_bridge);
+		bridge_merge_do(dst_bridge, src_bridge, NULL, 0);
 		--src_bridge->inhibit_merge;
 		--dst_bridge->inhibit_merge;
 		res = 0;
@@ -3155,17 +3171,219 @@
 	return res;
 }
 
+/*!
+ * \internal
+ * \brief Lock the local channel stack for chan and prequalify it.
+ * \since 12.0.0
+ *
+ * \param chan Local channel writing a frame into the channel driver.
+ *
+ * \note It is assumed that chan is already locked.
+ *
+ * \retval bridge on success with bridge and bridge_channel locked.
+ * \retval NULL if cannot do optimization now.
+ */
+static struct ast_bridge *optimize_lock_chan_stack(struct ast_channel *chan)
+{
+	struct ast_bridge *bridge;
+	struct ast_bridge_channel *bridge_channel;
+
+	if (!AST_LIST_EMPTY(ast_channel_readq(chan))) {
+		return NULL;
+	}
+	bridge_channel = ast_channel_internal_bridge_channel(chan);
+	if (!bridge_channel || ast_bridge_channel_trylock(bridge_channel)) {
+		return NULL;
+	}
+	bridge = bridge_channel->bridge;
+	if (bridge_channel->activity != AST_BRIDGE_CHANNEL_THREAD_SIMPLE
+		|| bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT
+		|| ast_bridge_trylock(bridge)) {
+		ast_bridge_channel_unlock(bridge_channel);
+		return NULL;
+	}
+	if (bridge->inhibit_merge
+		|| bridge->dissolved
+		|| ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)
+		|| !AST_LIST_EMPTY(&bridge_channel->wr_queue)) {
+		ast_bridge_unlock(bridge);
+		ast_bridge_channel_unlock(bridge_channel);
+		return NULL;
+	}
+	return bridge;
+}
+
+/*!
+ * \internal
+ * \brief Lock the local channel stack for peer and prequalify it.
+ * \since 12.0.0
+ *
+ * \param peer Other local channel in the pair.
+ *
+ * \retval bridge on success with bridge, bridge_channel, and peer locked.
+ * \retval NULL if cannot do optimization now.
+ */
+static struct ast_bridge *optimize_lock_peer_stack(struct ast_channel *peer)
+{
+	struct ast_bridge *bridge;
+	struct ast_bridge_channel *bridge_channel;
+
+	if (ast_channel_trylock(peer)) {
+		return NULL;
+	}
+	if (!AST_LIST_EMPTY(ast_channel_readq(peer))) {
+		ast_channel_unlock(peer);
+		return NULL;
+	}
+	bridge_channel = ast_channel_internal_bridge_channel(peer);
+	if (!bridge_channel || ast_bridge_channel_trylock(bridge_channel)) {
+		ast_channel_unlock(peer);
+		return NULL;
+	}
+	bridge = bridge_channel->bridge;
+	if (bridge_channel->activity != AST_BRIDGE_CHANNEL_THREAD_IDLE
+		|| bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT
+		|| ast_bridge_trylock(bridge)) {
+		ast_bridge_channel_unlock(bridge_channel);
+		ast_channel_unlock(peer);
+		return NULL;
+	}
+	if (bridge->inhibit_merge
+		|| bridge->dissolved
+		|| ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)
+		|| !AST_LIST_EMPTY(&bridge_channel->wr_queue)) {
+		ast_bridge_unlock(bridge);
+		ast_bridge_channel_unlock(bridge_channel);
+		ast_channel_unlock(peer);
+		return NULL;
+	}
+	return bridge;
+}
+
+int ast_bridge_local_optimized_out(struct ast_channel *chan, struct ast_channel *peer)
+{
+	struct ast_bridge *chan_bridge;
+	struct ast_bridge *peer_bridge;
+	struct ast_bridge_channel *chan_bridge_channel;
+	struct ast_bridge_channel *peer_bridge_channel;
+	int res = 0;
+
+	chan_bridge = optimize_lock_chan_stack(chan);
+	if (!chan_bridge) {
+		return res;
+	}
+	chan_bridge_channel = ast_channel_internal_bridge_channel(chan);
+
+	peer_bridge = optimize_lock_peer_stack(peer);
+	if (peer_bridge) {
+		struct ast_bridge *dst_bridge = NULL;
+		struct ast_bridge *src_bridge = NULL;
+
+		peer_bridge_channel = ast_channel_internal_bridge_channel(peer);
+
+		if (!ast_test_flag(&chan_bridge->feature_flags,
+				AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)
+			&& !ast_test_flag(&peer_bridge->feature_flags,
+				AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)) {
+/* BUGBUG need to have a merge priority bridge class method to help determine which way to optimize merge. */
+			/* Can merge either way. */
+			if (peer_bridge->num_channels <= chan_bridge->num_channels) {
+				/* Merge to the larger bridge. */
+				dst_bridge = chan_bridge;
+				src_bridge = peer_bridge;
+			} else {
+				dst_bridge = peer_bridge;
+				src_bridge = chan_bridge;
+			}
+		} else if (!ast_test_flag(&chan_bridge->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_TO)
+			&& !ast_test_flag(&peer_bridge->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)) {
+			/* Can merge only one way. */
+			dst_bridge = chan_bridge;
+			src_bridge = peer_bridge;
+		} else if (!ast_test_flag(&peer_bridge->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_TO)
+			&& !ast_test_flag(&chan_bridge->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)) {
+			/* Can merge only one way. */
+			dst_bridge = peer_bridge;
+			src_bridge = chan_bridge;
+		}
+
+		if (dst_bridge) {
+			if (src_bridge->num_channels < 2 || dst_bridge->num_channels < 2) {
+				ast_debug(4, "Can't optimize %s -- %s out, not enough channels in a bridge.\n",
+					ast_channel_name(chan), ast_channel_name(peer));
+			} else if ((2 + 2) < dst_bridge->num_channels + src_bridge->num_channels
+				&& !(dst_bridge->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX)
+				&& !ast_test_flag(&dst_bridge->feature_flags, AST_BRIDGE_FLAG_SMART)) {
+				ast_debug(4, "Can't optimize %s -- %s out, multimix is needed and it cannot be acquired.\n",
+					ast_channel_name(chan), ast_channel_name(peer));
+			} else {
+				struct ast_bridge_channel *kick_me[] = {
+					chan_bridge_channel,
+					peer_bridge_channel,
+					};
+
+				ast_debug(1, "Optimizing %s -- %s out.\n",
+					ast_channel_name(chan), ast_channel_name(peer));
+
+				bridge_merge_do(dst_bridge, src_bridge, kick_me, ARRAY_LEN(kick_me));
+				res = -1;
+			}
+		} else {
+/* BUGBUG this is where we would decide if we can swap a channel instead of merging. */
+		}
+
+		/* Release peer locks. */
+		ast_bridge_unlock(peer_bridge);
+		ast_bridge_channel_unlock(peer_bridge_channel);
+		ast_channel_unlock(peer);
+	}
+
+	/* Release chan locks. */
+	ast_bridge_unlock(chan_bridge);
+	ast_bridge_channel_unlock(chan_bridge_channel);
+
+	return res;
+}
+
+/*!
+ * \internal
+ * \brief Adjust the bridge merge inhibit request count.
+ * \since 12.0.0
+ *
+ * \param bridge What to operate on.
+ * \param request Inhibit request increment.
+ *     (Positive to add requests.  Negative to remove requests.)
+ *
+ * \note This function assumes bridge is locked.
+ *
+ * \return Nothing
+ */
+static void bridge_merge_inhibit_nolock(struct ast_bridge *bridge, int request)
+{
+	int new_request;
+
+	new_request = bridge->inhibit_merge + request;
+	ast_assert(0 <= new_request);
+	bridge->inhibit_merge = new_request;
+}
+
 void ast_bridge_merge_inhibit(struct ast_bridge *bridge, int request)
 {
-	int new_request;
-
 	ast_bridge_lock(bridge);
-	new_request = bridge->inhibit_merge + request;
-	if (new_request < 0) {
-		new_request = 0;
-	}
-	bridge->inhibit_merge = new_request;
+	bridge_merge_inhibit_nolock(bridge, request);
 	ast_bridge_unlock(bridge);
+}
+
+struct ast_bridge *ast_bridge_channel_merge_inhibit(struct ast_bridge_channel *bridge_channel, int request)
+{
+	struct ast_bridge *bridge;
+
+	ast_bridge_channel_lock_bridge(bridge_channel);
+	bridge = bridge_channel->bridge;
+	ao2_ref(bridge, +1);
+	bridge_merge_inhibit_nolock(bridge, request);
+	ast_bridge_unlock(bridge);
+	return bridge;
 }
 
 int ast_bridge_suspend(struct ast_bridge *bridge, struct ast_channel *chan)




More information about the asterisk-commits mailing list