summaryrefslogtreecommitdiff
path: root/main/bridge.c
diff options
context:
space:
mode:
authorKevin Harwell <kharwell@digium.com>2015-07-08 14:56:10 -0500
committerKevin Harwell <kharwell@digium.com>2015-07-13 12:57:56 -0500
commitc8555235195ed1deb37f5e27390b0ed4da329dc5 (patch)
tree45d9a85a0858cf8e0dc9daeab658dcc3f6ef3f08 /main/bridge.c
parent66b57b10f65bee8600c01a0fc03fb491edb7ad76 (diff)
bridge.c: Fixed race condition during attended transfer
During an attended transfer a thread is started that handles imparting the bridge channel. From the start of the thread to when the bridge channel is ready exists a gap that can potentially cause problems (for instance, the channel being swapped is hung up before the replacement channel enters the bridge thus stopping the transfer). This patch adds a condition that waits for the impart thread to get to a point of acceptable readiness before allowing the initiating thread to continue. ASTERISK-24782 Reported by: John Bigelow Change-Id: I08fe33a2560da924e676df55b181e46fca604577
Diffstat (limited to 'main/bridge.c')
-rw-r--r--main/bridge.c51
1 files changed, 44 insertions, 7 deletions
diff --git a/main/bridge.c b/main/bridge.c
index b2f7b0ffa..ebbfc3976 100644
--- a/main/bridge.c
+++ b/main/bridge.c
@@ -1549,7 +1549,7 @@ int ast_bridge_join(struct ast_bridge *bridge,
}
if (!res) {
- res = bridge_channel_internal_join(bridge_channel);
+ res = bridge_channel_internal_join(bridge_channel, NULL);
}
/* Cleanup all the data in the bridge channel after it leaves the bridge. */
@@ -1579,13 +1579,14 @@ join_exit:;
/*! \brief Thread responsible for imparted bridged channels to be departed */
static void *bridge_channel_depart_thread(void *data)
{
- struct ast_bridge_channel *bridge_channel = data;
+ struct bridge_channel_internal_cond *cond = data;
+ struct ast_bridge_channel *bridge_channel = cond->bridge_channel;
if (bridge_channel->callid) {
ast_callid_threadassoc_add(bridge_channel->callid);
}
- bridge_channel_internal_join(bridge_channel);
+ bridge_channel_internal_join(bridge_channel, cond);
/*
* cleanup
@@ -1606,14 +1607,15 @@ static void *bridge_channel_depart_thread(void *data)
/*! \brief Thread responsible for independent imparted bridged channels */
static void *bridge_channel_ind_thread(void *data)
{
- struct ast_bridge_channel *bridge_channel = data;
+ struct bridge_channel_internal_cond *cond = data;
+ struct ast_bridge_channel *bridge_channel = cond->bridge_channel;
struct ast_channel *chan;
if (bridge_channel->callid) {
ast_callid_threadassoc_add(bridge_channel->callid);
}
- bridge_channel_internal_join(bridge_channel);
+ bridge_channel_internal_join(bridge_channel, cond);
chan = bridge_channel->chan;
/* cleanup */
@@ -1697,13 +1699,27 @@ int ast_bridge_impart(struct ast_bridge *bridge,
/* Actually create the thread that will handle the channel */
if (!res) {
+ struct bridge_channel_internal_cond cond = {
+ .done = 0,
+ .bridge_channel = bridge_channel
+ };
+ ast_mutex_init(&cond.lock);
+ ast_cond_init(&cond.cond, NULL);
+
if ((flags & AST_BRIDGE_IMPART_CHAN_MASK) == AST_BRIDGE_IMPART_CHAN_INDEPENDENT) {
res = ast_pthread_create_detached(&bridge_channel->thread, NULL,
- bridge_channel_ind_thread, bridge_channel);
+ bridge_channel_ind_thread, &cond);
} else {
res = ast_pthread_create(&bridge_channel->thread, NULL,
- bridge_channel_depart_thread, bridge_channel);
+ bridge_channel_depart_thread, &cond);
}
+
+ if (!res) {
+ bridge_channel_internal_wait(&cond);
+ }
+
+ ast_cond_destroy(&cond.cond);
+ ast_mutex_destroy(&cond.lock);
}
if (res) {
@@ -3953,6 +3969,15 @@ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *cha
struct ast_channel *chan2, struct ast_bridge *bridge1, struct ast_bridge *bridge2,
struct ast_attended_transfer_message *transfer_msg)
{
+#define BRIDGE_LOCK_ONE_OR_BOTH(b1, b2) \
+ do { \
+ if (b2) { \
+ ast_bridge_lock_both(b1, b2); \
+ } else { \
+ ast_bridge_lock(b1); \
+ } \
+ } while (0)
+
static const char *dest = "_attended@transfer/m";
struct ast_channel *local_chan;
int cause;
@@ -3983,8 +4008,18 @@ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *cha
return AST_BRIDGE_TRANSFER_FAIL;
}
+ /*
+ * Since bridges need to be unlocked before entering ast_bridge_impart and
+ * core_local may call into it then the bridges need to be unlocked here.
+ */
+ ast_bridge_unlock(bridge1);
+ if (bridge2) {
+ ast_bridge_unlock(bridge2);
+ }
+
if (ast_call(local_chan, dest, 0)) {
ast_hangup(local_chan);
+ BRIDGE_LOCK_ONE_OR_BOTH(bridge1, bridge2);
return AST_BRIDGE_TRANSFER_FAIL;
}
@@ -3994,8 +4029,10 @@ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *cha
AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) {
ast_hangup(local_chan);
ao2_cleanup(local_chan);
+ BRIDGE_LOCK_ONE_OR_BOTH(bridge1, bridge2);
return AST_BRIDGE_TRANSFER_FAIL;
}
+ BRIDGE_LOCK_ONE_OR_BOTH(bridge1, bridge2);
if (bridge2) {
RAII_VAR(struct ast_channel *, local_chan2, NULL, ao2_cleanup);