From 45c3ac0436427a6c730837ad2d38ab2cb2189e8c Mon Sep 17 00:00:00 2001
From: Grzegorz Sluja <grzegorz.sluja@iopsys.eu>
Date: Tue, 21 Sep 2021 11:13:16 +0200
Subject: [PATCH] Avoid deadlock during R4 call transfer

There was a possibility that during attended call transfer, deadlock
happened in locking bridge_channel and channel.
bridge_channel_queue_deferred_frames() was waiting for brcm_indicate()
to unlock the channel, while brcm_indicate() which called
ast_bridge_channel_queue_frame() was waiting for
bridge_channel_queue_deferred_frames() to unlock the bridge.

Creating new macro to lock both channel and bridge_channel at the same
time and sched_yield() -> retry if failed and use it in
bridge_channel_queue_deferred_frames() avoiding the deadlock.
---
 include/asterisk/bridge_channel.h | 13 +++++++++++++
 main/bridge_channel.c             |  3 +--
 2 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/include/asterisk/bridge_channel.h b/include/asterisk/bridge_channel.h
index fb2fd4f6f3..dc2735f308 100644
--- a/include/asterisk/bridge_channel.h
+++ b/include/asterisk/bridge_channel.h
@@ -247,6 +247,19 @@ static inline void _ast_bridge_channel_unlock(struct ast_bridge_channel *bridge_
 	__ao2_unlock(bridge_channel, file, function, line, var);
 }
 
+/*! \brief Lock bridge_channel and channel at the same time. */
+#define ast_bridge_lock_channel_and_bridge_channel(bridge_channel, channel)	\
+	do {									\
+		for (;;) {							\
+			ast_bridge_channel_lock(bridge_channel);		\
+			if (!ast_channel_trylock(channel)) {			\
+				break;						\
+			}							\
+			ast_bridge_channel_unlock(bridge_channel);		\
+			sched_yield();						\
+		}								\
+	} while (0)
+
 /*!
  * \brief Lock the bridge associated with the bridge channel.
  * \since 12.0.0
diff --git a/main/bridge_channel.c b/main/bridge_channel.c
index d39e3e16ee..ffacbed1dc 100644
--- a/main/bridge_channel.c
+++ b/main/bridge_channel.c
@@ -832,8 +832,7 @@ void bridge_channel_queue_deferred_frames(struct ast_bridge_channel *bridge_chan
 {
 	struct ast_frame *frame;
 
-	ast_bridge_channel_lock(bridge_channel);
-	ast_channel_lock(bridge_channel->chan);
+	ast_bridge_lock_channel_and_bridge_channel(bridge_channel, bridge_channel->chan);
 	while ((frame = AST_LIST_REMOVE_HEAD(&bridge_channel->deferred_queue, frame_list))) {
 		ast_queue_frame_head(bridge_channel->chan, frame);
 		ast_frfree(frame);
-- 
GitLab