diff --git a/include/asterisk/core_local.h b/include/asterisk/core_local.h index 491112d2bf..8557072c69 100644 --- a/include/asterisk/core_local.h +++ b/include/asterisk/core_local.h @@ -41,6 +41,38 @@ struct stasis_message_type; /* ------------------------------------------------------------------- */ +/*! + * \brief Lock the "chan" and "owner" channels (and return them) on the base + * private structure as well as the base private structure itself. + * + * \note This also adds references to each of the above mentioned elements and + * also the underlying private local structure. + * \note None of these locks should be held prior to calling this function. + * \note To undo this process call ast_local_unlock_all. + * + * \since 13.8.0 + * + * \param chan Must be a local channel + * \param outchan The local channel's "chan" channel + * \param outowner The local channel's "owner" channel + */ +void ast_local_lock_all(struct ast_channel *chan, struct ast_channel **outchan, + struct ast_channel **outowner); + +/*! + * \brief Unlock the "chan" and "owner" channels on the base private structure + * as well as the base private structure itself. + * + * \note This also removes references to each of the above mentioned elements and + * also the underlying private local structure. + * \note This function should be used in conjunction with ast_local_lock_all. + * + * \since 13.8.0 + * + * \param chan Must be a local channel + */ +void ast_local_unlock_all(struct ast_channel *chan); + /*! * \brief Get the other local channel in the pair. * \since 12.0.0 diff --git a/main/bridge.c b/main/bridge.c index 77865757ea..b9a436e86a 100644 --- a/main/bridge.c +++ b/main/bridge.c @@ -4034,19 +4034,25 @@ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *cha BRIDGE_LOCK_ONE_OR_BOTH(bridge1, bridge2); if (bridge2) { - RAII_VAR(struct ast_channel *, local_chan2, NULL, ao2_cleanup); struct ast_channel *locals[2]; - ast_channel_lock(local_chan); - local_chan2 = ast_local_get_peer(local_chan); - ast_channel_unlock(local_chan); + /* Have to lock everything just in case a hangup comes in early */ + ast_local_lock_all(local_chan, &locals[0], &locals[1]); + if (!locals[0] || !locals[1]) { + ast_log(LOG_ERROR, "Transfer failed probably due to an early hangup - " + "missing other half of '%s'\n", ast_channel_name(local_chan)); + ast_local_unlock_all(local_chan); + ao2_cleanup(local_chan); + return AST_BRIDGE_TRANSFER_FAIL; + } - ast_assert(local_chan2 != NULL); - - locals[0] = local_chan; - locals[1] = local_chan2; + /* Make sure the peer is properly set */ + if (local_chan != locals[0]) { + SWAP(locals[0], locals[1]); + } ast_attended_transfer_message_add_link(transfer_msg, locals); + ast_local_unlock_all(local_chan); } else { ast_attended_transfer_message_add_app(transfer_msg, app, local_chan); } diff --git a/main/core_local.c b/main/core_local.c index f81c71cd61..6644aaf509 100644 --- a/main/core_local.c +++ b/main/core_local.c @@ -235,6 +235,45 @@ struct local_pvt { char exten[AST_MAX_EXTENSION]; }; +void ast_local_lock_all(struct ast_channel *chan, struct ast_channel **outchan, + struct ast_channel **outowner) +{ + struct local_pvt *p = ast_channel_tech_pvt(chan); + + *outchan = NULL; + *outowner = NULL; + + if (p) { + ao2_ref(p, 1); + ast_unreal_lock_all(&p->base, outchan, outowner); + } +} + +void ast_local_unlock_all(struct ast_channel *chan) +{ + struct local_pvt *p = ast_channel_tech_pvt(chan); + struct ast_unreal_pvt *base; + + if (!p) { + return; + } + + base = &p->base; + + if (base->owner) { + ast_channel_unlock(base->owner); + ast_channel_unref(base->owner); + } + + if (base->chan) { + ast_channel_unlock(base->chan); + ast_channel_unref(base->chan); + } + + ao2_unlock(base); + ao2_ref(p, -1); +} + struct ast_channel *ast_local_get_peer(struct ast_channel *ast) { struct local_pvt *p = ast_channel_tech_pvt(ast);