summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Harwell <kharwell@digium.com>2017-06-20 16:05:08 -0500
committerKevin Harwell <kharwell@digium.com>2017-06-21 16:18:13 -0500
commit27dae55fb6bf802befe6f398d02f278f2c31114c (patch)
treeec5d10c92d43dde00d95856bb7d99c0304c1f07d
parent0c0d69d4f38756c2f83d2c9007033e0ca05652c4 (diff)
core_local: local channel data not being properly unref'ed and unlocked
In an earlier version of Asterisk a local channel [un]lock all functions were added in order to keep a crash from occurring when a channel hung up too early during an attended transfer. Unfortunately, when a transfer failure occurs and depending on the timing, the local channels sometime do not get properly unlocked and deref'ed after being locked and ref'ed. This happens because the underlying local channel structure gets NULLed out before unlocking. This patch reworks those [un]lock functions and makes sure the values that get locked and ref'ed later get unlocked and deref'ed. ASTERISK-27074 #close Change-Id: Ice96653e29bd9d6674ed5f95feb6b448ab148b09
-rw-r--r--include/asterisk/core_local.h37
-rw-r--r--main/bridge.c7
-rw-r--r--main/core_local.c42
3 files changed, 42 insertions, 44 deletions
diff --git a/include/asterisk/core_local.h b/include/asterisk/core_local.h
index 8557072c6..27e924477 100644
--- a/include/asterisk/core_local.h
+++ b/include/asterisk/core_local.h
@@ -42,36 +42,37 @@ 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.
+ * \brief Add a reference to the local channel's private tech, lock the local channel's
+ * private base, and add references and lock both sides of the local channel.
*
- * \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.
+ * \note To undo this process call ast_local_unlock_all2.
*
- * \since 13.8.0
+ * \since 13.17.0, 14.6.0
*
* \param chan Must be a local channel
- * \param outchan The local channel's "chan" channel
- * \param outowner The local channel's "owner" channel
+ * \param tech_pvt [out] channel's private tech (ref and lock added)
+ * \param base_chan [out] One side of the local channel (ref and lock added)
+ * \param base_owner [out] Other side of the local channel (ref and lock added)
*/
-void ast_local_lock_all(struct ast_channel *chan, struct ast_channel **outchan,
- struct ast_channel **outowner);
+void ast_local_lock_all(struct ast_channel *chan, void **tech_pvt,
+ struct ast_channel **base_chan, struct ast_channel **base_owner);
/*!
- * \brief Unlock the "chan" and "owner" channels on the base private structure
- * as well as the base private structure itself.
+ * \brief Remove a reference to the given local channel's private tech, unlock the given
+ * local channel's private base, and remove references and unlock both sides of
+ * given the local channel.
*
- * \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.
+ * \note This function should be used in conjunction with ast_local_lock_all2.
*
- * \since 13.8.0
+ * \since 13.17.0, 14.6.0
*
- * \param chan Must be a local channel
+ * \param tech_pvt channel's private tech (ref and lock removed)
+ * \param base_chan One side of the local channel (ref and lock removed)
+ * \param base_owner Other side of the local channel (ref and lock removed)
*/
-void ast_local_unlock_all(struct ast_channel *chan);
+void ast_local_unlock_all(void *tech_pvt, struct ast_channel *base_chan,
+ struct ast_channel *base_owner);
/*!
* \brief Get the other local channel in the pair.
diff --git a/main/bridge.c b/main/bridge.c
index 8cde62cb5..8902145cf 100644
--- a/main/bridge.c
+++ b/main/bridge.c
@@ -4276,14 +4276,15 @@ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *cha
BRIDGE_LOCK_ONE_OR_BOTH(bridge1, bridge2);
if (bridge2) {
+ void *tech;
struct ast_channel *locals[2];
/* Have to lock everything just in case a hangup comes in early */
- ast_local_lock_all(local_chan, &locals[0], &locals[1]);
+ ast_local_lock_all(local_chan, &tech, &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);
+ ast_local_unlock_all(tech, locals[0], locals[1]);
ao2_cleanup(local_chan);
return AST_BRIDGE_TRANSFER_FAIL;
}
@@ -4294,7 +4295,7 @@ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *cha
}
ast_attended_transfer_message_add_link(transfer_msg, locals);
- ast_local_unlock_all(local_chan);
+ ast_local_unlock_all(tech, locals[0], locals[1]);
} 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 aa232a4b6..23c7cce9d 100644
--- a/main/core_local.c
+++ b/main/core_local.c
@@ -233,43 +233,39 @@ 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)
+void ast_local_lock_all(struct ast_channel *chan, void **tech_pvt,
+ struct ast_channel **base_chan, struct ast_channel **base_owner)
{
struct local_pvt *p = ast_channel_tech_pvt(chan);
- *outchan = NULL;
- *outowner = NULL;
+ *tech_pvt = NULL;
+ *base_chan = NULL;
+ *base_owner = NULL;
if (p) {
- ao2_ref(p, 1);
- ast_unreal_lock_all(&p->base, outchan, outowner);
+ *tech_pvt = ao2_bump(p);
+ ast_unreal_lock_all(&p->base, base_chan, base_owner);
}
}
-void ast_local_unlock_all(struct ast_channel *chan)
+void ast_local_unlock_all(void *tech_pvt, struct ast_channel *base_chan,
+ struct ast_channel *base_owner)
{
- struct local_pvt *p = ast_channel_tech_pvt(chan);
- struct ast_unreal_pvt *base;
-
- if (!p) {
- return;
+ if (base_chan) {
+ ast_channel_unlock(base_chan);
+ ast_channel_unref(base_chan);
}
- base = &p->base;
-
- if (base->owner) {
- ast_channel_unlock(base->owner);
- ast_channel_unref(base->owner);
+ 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);
+ if (tech_pvt) {
+ struct local_pvt *p = tech_pvt;
+ ao2_unlock(&p->base);
+ ao2_ref(tech_pvt, -1);
}
-
- ao2_unlock(base);
- ao2_ref(p, -1);
}
struct ast_channel *ast_local_get_peer(struct ast_channel *ast)