From 772985be5e82b7ee33e1a823fa7e1de01a31f292 Mon Sep 17 00:00:00 2001
From: Wenpeng Song <wenpeng.song@iopsys.eu>
Date: Wed, 31 Jan 2024 13:06:16 +0000
Subject: [PATCH] Fix a dead lock and remove some duplicated indications during
 un-hold

Fix a dead lock during un-hold procedure under race condition by
  * Remove duplicated indications for un-hold.
  * Narrow the usage of channel lock inside `chan_voicemngr_modify_codec
  * Remove the extra redirection for the case from: https://dev.iopsys.eu/voice/asterisk-chan-
    voicemngr/-/merge_requests/26 as it is not needed for now.

The dead lock is due to `ast_channel_get_by_name` used by `chan_voicemngr_modify_codec`(which
called during ssrc related indications) https://dev.iopsys.eu/voice/asterisk-chan-
voicemngr/-/blob/devel/src/channels/chan_voicemngr.c#L774 has a channel lock from
`ast_channel_by_name_cb`. And indications for different channels also have locks from
`ast_indicate_data`. They may waiting for each other during some massive indications between
bridged channels.


(cherry picked from commit 1e3a03a82ed730eccdee999b7ac019043e07d148)
---
 src/channels/chan_voicemngr.c | 44 +++++++++++++++++++++++------------
 1 file changed, 29 insertions(+), 15 deletions(-)

diff --git a/src/channels/chan_voicemngr.c b/src/channels/chan_voicemngr.c
index fe1bf68..b75815d 100644
--- a/src/channels/chan_voicemngr.c
+++ b/src/channels/chan_voicemngr.c
@@ -766,6 +766,7 @@ static void chan_voicemngr_modify_codec(struct chan_voicemngr_subchannel *sub) {
 		ast_debug(4, "sub->owner Channel %s, ast_channel_codec_get(): %s\n",ast_channel_name(sub->owner), ast_channel_codec_get(sub->owner));
 		ast_channel_lock(sub->owner);
 		struct ast_bridge *bridge = ast_channel_internal_bridge(sub->owner);
+		ast_channel_unlock(sub->owner);
 		struct ast_bridge_channel *bridge_channel;
 		struct ast_channel *bridged_chan=NULL;
 		if(bridge){
@@ -780,32 +781,41 @@ static void chan_voicemngr_modify_codec(struct chan_voicemngr_subchannel *sub) {
 		if (bridged_chan) {
 			ast_debug(5, "Got bridged_chan\n");
 			// bridged_chan is allocated but codec and ptime is not set so need to check it here and set default
+			ast_channel_lock(sub->owner);
 			ast_channel_ptime_set(sub->owner, ast_channel_ptime_get(bridged_chan) ? ast_channel_ptime_get(bridged_chan) : default_ptime);
+			ast_channel_unlock(sub->owner);
 			if (strncmp(ast_channel_name(bridged_chan), "TELCHAN", 5) == 0 ) {
 				// local chan, internal call, using alaw.
 				ast_log(LOG_NOTICE, "INTERNAL CALL, %s\n", ast_channel_name(bridged_chan));
+				ast_channel_lock(sub->owner);
 				ast_channel_codec_set(sub->owner, "alaw");
+				ast_channel_unlock(sub->owner);
+				ast_channel_lock(bridged_chan);
 				ast_channel_codec_set(bridged_chan, "alaw");
+				ast_channel_unlock(bridged_chan);
 				// set sip client to 0 for internal call
 				sub->sip_client_id = 0;
 				struct ast_format_cap *caps;
 				caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
 				if (caps) {
-				    ast_format_cap_append(caps, map_rtpname_to_format("alaw"), 0);
+					ast_format_cap_append(caps, map_rtpname_to_format("alaw"), 0);
+					ast_channel_lock(bridged_chan);
 					ast_channel_nativeformats_set(bridged_chan, caps);
+					ast_channel_unlock(bridged_chan);
 					ao2_ref(caps, -1);
 				} else {
 					ao2_cleanup(caps);
 				}
 			} else {
 			// get codec from bridged_chan's writeformat if not set
-			    ast_channel_codec_set(sub->owner, !ast_strlen_zero(ast_channel_codec_get(bridged_chan)) ?
-				    ast_channel_codec_get(bridged_chan) : ast_format_get_name(ast_channel_writeformat(bridged_chan)));
+				ast_channel_lock(sub->owner);
+				ast_channel_codec_set(sub->owner, !ast_strlen_zero(ast_channel_codec_get(bridged_chan)) ?
+					ast_channel_codec_get(bridged_chan) : ast_format_get_name(ast_channel_writeformat(bridged_chan)));
+				ast_channel_unlock(sub->owner);
 			}
 			ast_channel_unref(bridged_chan);
 		} else if (ast_strlen_zero(ast_channel_codec_get(sub->owner))) {
 			//return if has no codec set
-			ast_channel_unlock(sub->owner);
 			return;
 		}
 		ast_debug(5, "set format\n");
@@ -814,7 +824,9 @@ static void chan_voicemngr_modify_codec(struct chan_voicemngr_subchannel *sub) {
 		caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
 		if (caps) {
 			ast_format_cap_append(caps, map_rtpname_to_format((char *)ast_channel_codec_get(sub->owner)), 0);
+			ast_channel_lock(sub->owner);
 			ast_channel_nativeformats_set(sub->owner, caps);
+			ast_channel_unlock(sub->owner);
 			ao2_ref(caps, -1);
 		} else {
 			ao2_cleanup(caps);
@@ -842,7 +854,6 @@ static void chan_voicemngr_modify_codec(struct chan_voicemngr_subchannel *sub) {
 		if (data.mask & UBUS_DATA_CODEC_BIT) {
 			sub->updated_codec = 1;
 		}
-		ast_channel_unlock(sub->owner);
 	}
 }
 
@@ -851,8 +862,6 @@ static int chan_voicemngr_indicate(struct ast_channel *ast, int condition, const
 	struct chan_voicemngr_subchannel *sub = ast_channel_tech_pvt(ast);
 	struct chan_voicemngr_subchannel *sub_peer = chan_voicemngr_subchannel_get_peer(sub);
 	struct ast_bridge_channel *play_bridge_channel;
-	struct ast_bridge *myBridge;
-	struct ast_frame astFrame;
 	int res = 0;
 
 	ast_log(LOG_NOTICE, "Channel %s gets an indication, condition = %d, sub->channel_state: %s, sub->codec: %d,ast_channel_codec_get(ast): %s\n",
@@ -870,12 +879,16 @@ static int chan_voicemngr_indicate(struct ast_channel *ast, int condition, const
 
 		sub->channel_state = INCALL;
 
+		/* not needed as pjsip handles this indication as well, keep it here in case of regression.
 		// Tell all participants to re-sync RTP stream.
+		struct ast_bridge *myBridge;
+		struct ast_frame astFrame;
 		myBridge = ast_channel_internal_bridge(ast);
 		memset(&astFrame, 0, sizeof astFrame);
 		astFrame.frametype = AST_FRAME_CONTROL;
 		astFrame.subclass.integer = AST_CONTROL_SRCUPDATE;
 		ast_bridge_queue_everyone_else(myBridge, NULL, &astFrame);
+		*/
 		pvt_unlock(sub->parent);
 		break;
 
@@ -933,11 +946,6 @@ static int chan_voicemngr_indicate(struct ast_channel *ast, int condition, const
 			} else {
 				ast_log(LOG_ERROR, "can't get the peer channel, unattended call transfer will not be proceeded\n");
 			}
-
-		} else if(sub->channel_state == INCALL) {
-			/* for some specific scenarios, unhold was wrong indicate as unhold_for_transfer from pjsip
-			 just redirect to unhold to make less effect to transfer */
-		    ast_indicate(sub->owner, AST_CONTROL_UNHOLD);
 		}
 		break;
 	case AST_CONTROL_TRANSFER:
@@ -2895,7 +2903,10 @@ static void handle_hookflash(struct chan_voicemngr_subchannel *sub, struct chan_
 			if (owner) {
 				if(bridge){
 					AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
-						ast_indicate(bridge_channel->chan, AST_CONTROL_HOLD);
+						if ((strstr(ast_channel_name(bridge_channel->chan), "TELCHAN") != NULL) && (strcmp(ast_channel_name(bridge_channel->chan), ast_channel_name(owner)))) {
+							// only indicate other local channels, as asterisk will handle the pjsip channels with the queued frame.
+							ast_indicate(bridge_channel->chan, AST_CONTROL_HOLD);
+						}
 					}
 				}
 
@@ -2937,8 +2948,11 @@ static void handle_hookflash(struct chan_voicemngr_subchannel *sub, struct chan_
 			if (peer_owner) {
 				if(bridge){
 					AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
-						ast_indicate(bridge_channel->chan, AST_CONTROL_UNHOLD);
-						ast_log(LOG_NOTICE,"Pick up old unhold\n");
+						if ((strstr(ast_channel_name(bridge_channel->chan), "TELCHAN") != NULL) && (strcmp(ast_channel_name(bridge_channel->chan), ast_channel_name(peer_owner)))) {
+							// only indicate other local channels, as asterisk will handle the pjsip channels with the queued frame.
+							ast_indicate(bridge_channel->chan, AST_CONTROL_UNHOLD);
+							ast_log(LOG_NOTICE,"Pick up old unhold\n");
+						}
 					}
 				}
 				chan_voicemngr_send_ubus_event("CALL_UNHOLD",p->line_id);
-- 
GitLab