summaryrefslogtreecommitdiff
path: root/res/stasis
diff options
context:
space:
mode:
authorMark Michelson <mmichelson@digium.com>2016-05-09 14:48:51 -0500
committerMark Michelson <mmichelson@digium.com>2016-05-27 09:08:49 -0500
commit88d997913faabe81f8b9e7bdaa56742be0d669b9 (patch)
treedde0bb91e5840514d684887bb8e5e10864e3dcc3 /res/stasis
parentf6c33771f660c3ad15bc554b355cb21e83c85e36 (diff)
ARI: Re-implement the ARI dial command, allowing for early bridging.
ARI dial had been implemented using the Dial API. This made great sense when dialing was 100% separate from bridging. However, if a channel were to be added to a bridge during the dial attempt, there would be a conflict between the dialing thread and the bridging thread. Each would be attempting to read frames from the dialed channel and act on them. The initial attempt to make the two play nice was to have the Dial API suspend the channel in the bridge and stay in charge of the channel until the dial was complete. The problem with this was that it was riddled with potential race conditions. It also was not well-suited for the case where the channel changed which bridge it was in during the dial. This new approach removes the use of the Dial API altogether. Instead, the channel we are dialing is placed into an invisible ARI dialing bridge. The bridge channel thread handles incoming frames from the channel. If the channel is added to a real bridge, it is departed from the invisible bridge and then added to the real bridge. Similarly, if the channel is removed from the real bridge, it is automatically added back to the invisible bridge if the dial attempt is still active. This approach keeps the threading simple by always having the channel being handled by bridge channel threads. ASTERISK-25925 Change-Id: I7750359ddf45fcd45eaec749c5b3822de4a8ddbb
Diffstat (limited to 'res/stasis')
-rw-r--r--res/stasis/control.c376
1 files changed, 316 insertions, 60 deletions
diff --git a/res/stasis/control.c b/res/stasis/control.c
index aa6866aee..b255477bf 100644
--- a/res/stasis/control.c
+++ b/res/stasis/control.c
@@ -28,6 +28,7 @@
ASTERISK_REGISTER_FILE()
#include "asterisk/stasis_channels.h"
+#include "asterisk/stasis_app.h"
#include "command.h"
#include "control.h"
@@ -43,6 +44,11 @@ ASTERISK_REGISTER_FILE()
AST_LIST_HEAD(app_control_rules, stasis_app_control_rule);
+/*!
+ * \brief Indicates if the Stasis app internals are being shut down
+ */
+static int shutting_down;
+
struct stasis_app_control {
ast_cond_t wait_cond;
/*! Queue of commands to dispatch on the channel */
@@ -78,10 +84,6 @@ struct stasis_app_control {
*/
struct stasis_app *app;
/*!
- * If channel is being dialed, the dial structure.
- */
- struct ast_dial *dial;
- /*!
* When set, /c app_stasis should exit and continue in the dialplan.
*/
int is_done:1;
@@ -825,6 +827,128 @@ struct ast_bridge *stasis_app_get_bridge(struct stasis_app_control *control)
}
}
+/*!
+ * \brief Singleton dial bridge
+ *
+ * The dial bridge is a holding bridge used to hold all
+ * outbound dialed channels that are not in any "real" ARI-created
+ * bridge. The dial bridge is invisible, meaning that it does not
+ * show up in channel snapshots, AMI or ARI output, and no events
+ * get raised for it.
+ *
+ * This is used to keep dialed channels confined to the bridging system
+ * and unify the threading model used for dialing outbound channels.
+ */
+static struct ast_bridge *dial_bridge;
+AST_MUTEX_DEFINE_STATIC(dial_bridge_lock);
+
+/*!
+ * \brief Retrieve a reference to the dial bridge.
+ *
+ * If the dial bridge has not been created yet, it will
+ * be created, otherwise, a reference to the existing bridge
+ * will be returned.
+ *
+ * The caller will need to unreference the dial bridge once
+ * they are finished with it.
+ *
+ * \retval NULL Unable to find/create the dial bridge
+ * \retval non-NULL A reference to teh dial bridge
+ */
+static struct ast_bridge *get_dial_bridge(void)
+{
+ struct ast_bridge *ret_bridge = NULL;
+
+ ast_mutex_lock(&dial_bridge_lock);
+
+ if (shutting_down) {
+ goto end;
+ }
+
+ if (dial_bridge) {
+ ret_bridge = ao2_bump(dial_bridge);
+ goto end;
+ }
+
+ dial_bridge = stasis_app_bridge_create_invisible("holding", "dial_bridge", NULL);
+ if (!dial_bridge) {
+ goto end;
+ }
+ ret_bridge = ao2_bump(dial_bridge);
+
+end:
+ ast_mutex_unlock(&dial_bridge_lock);
+ return ret_bridge;
+}
+
+/*!
+ * \brief after bridge callback for the dial bridge
+ *
+ * The only purpose of this callback is to ensure that the control structure's
+ * bridge pointer is NULLed
+ */
+static void dial_bridge_after_cb(struct ast_channel *chan, void *data)
+{
+ struct stasis_app_control *control = data;
+
+ control->bridge = NULL;
+}
+
+static void dial_bridge_after_cb_failed(enum ast_bridge_after_cb_reason reason, void *data)
+{
+ struct stasis_app_control *control = data;
+
+ dial_bridge_after_cb(control->channel, data);
+}
+
+/*!
+ * \brief Add a channel to the singleton dial bridge.
+ *
+ * \param control The Stasis control structure
+ * \param chan The channel to add to the bridge
+ * \retval -1 Failed
+ * \retval 0 Success
+ */
+static int add_to_dial_bridge(struct stasis_app_control *control, struct ast_channel *chan)
+{
+ struct ast_bridge *bridge;
+
+ bridge = get_dial_bridge();
+ if (!bridge) {
+ return -1;
+ }
+
+ control->bridge = bridge;
+ ast_bridge_set_after_callback(chan, dial_bridge_after_cb, dial_bridge_after_cb_failed, control);
+ if (ast_bridge_impart(bridge, chan, NULL, NULL, AST_BRIDGE_IMPART_CHAN_DEPARTABLE)) {
+ control->bridge = NULL;
+ ao2_ref(bridge, -1);
+ return -1;
+ }
+
+ ao2_ref(bridge, -1);
+
+ return 0;
+}
+
+/*!
+ * \brief Depart a channel from a bridge, and potentially add it back to the dial bridge
+ *
+ * \param control Take a guess
+ * \param chan Take another guess
+ */
+static int depart_channel(struct stasis_app_control *control, struct ast_channel *chan)
+{
+ ast_bridge_depart(chan);
+
+ if (!ast_check_hangup(chan) && ast_channel_state(chan) != AST_STATE_UP) {
+ /* Channel is still being dialed, so put it back in the dialing bridge */
+ add_to_dial_bridge(control, chan);
+ }
+
+ return 0;
+}
+
static int bridge_channel_depart(struct stasis_app_control *control,
struct ast_channel *chan, void *data)
{
@@ -843,7 +967,7 @@ static int bridge_channel_depart(struct stasis_app_control *control,
ast_debug(3, "%s: Channel departing bridge\n",
ast_channel_uniqueid(chan));
- ast_bridge_depart(chan);
+ depart_channel(control, chan);
return 0;
}
@@ -903,6 +1027,107 @@ static void bridge_after_cb_failed(enum ast_bridge_after_cb_reason reason,
ast_bridge_after_cb_reason_string(reason));
}
+/*!
+ * \brief Dial timeout datastore
+ *
+ * A datastore is used because a channel may change
+ * bridges during the course of a dial attempt. This
+ * may be because the channel changes from the dial bridge
+ * to a standard bridge, or it may move between standard
+ * bridges. In order to keep the dial timeout, we need
+ * to keep the timeout information local to the channel.
+ * That is what this datastore is for
+ */
+struct ast_datastore_info timeout_datastore = {
+ .type = "ARI dial timeout",
+};
+
+static int hangup_channel(struct stasis_app_control *control,
+ struct ast_channel *chan, void *data)
+{
+ ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT);
+ return 0;
+}
+
+/*!
+ * \brief Dial timeout
+ *
+ * This is a bridge interval hook callback. The interval hook triggering
+ * means that the dial timeout has been reached. If the channel has not
+ * been answered by the time this callback is called, then the channel
+ * is hung up
+ *
+ * \param bridge_channel Bridge channel on which interval hook has been called
+ * \param ignore Ignored
+ * \return -1 (i.e. remove the interval hook)
+ */
+static int bridge_timeout(struct ast_bridge_channel *bridge_channel, void *ignore)
+{
+ struct ast_datastore *datastore;
+ RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
+
+ control = stasis_app_control_find_by_channel(bridge_channel->chan);
+
+ ast_channel_lock(bridge_channel->chan);
+ if (ast_channel_state(bridge_channel->chan) != AST_STATE_UP) {
+ /* Don't bother removing the datastore because it will happen when the channel is hung up */
+ ast_channel_unlock(bridge_channel->chan);
+ stasis_app_send_command_async(control, hangup_channel, NULL, NULL);
+ return -1;
+ }
+
+ datastore = ast_channel_datastore_find(bridge_channel->chan, &timeout_datastore, NULL);
+ if (!datastore) {
+ ast_channel_unlock(bridge_channel->chan);
+ return -1;
+ }
+ ast_channel_datastore_remove(bridge_channel->chan, datastore);
+ ast_channel_unlock(bridge_channel->chan);
+ ast_datastore_free(datastore);
+
+ return -1;
+}
+
+/*!
+ * \brief Set a dial timeout interval hook on the channel.
+ *
+ * The absolute time that the timeout should occur is stored on
+ * a datastore on the channel. This time is converted into a relative
+ * number of milliseconds in the future. Then an interval hook is set
+ * to trigger in that number of milliseconds.
+ *
+ * \pre chan is locked
+ *
+ * \param chan The channel on which to set the interval hook
+ */
+static void set_interval_hook(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore;
+ struct timeval *hangup_time;
+ int64_t ms;
+ struct ast_bridge_channel *bridge_channel;
+
+ datastore = ast_channel_datastore_find(chan, &timeout_datastore, NULL);
+ if (!datastore) {
+ return;
+ }
+
+ hangup_time = datastore->data;
+
+ ms = ast_tvdiff_ms(*hangup_time, ast_tvnow());
+ bridge_channel = ast_channel_get_bridge_channel(chan);
+ if (!bridge_channel) {
+ return;
+ }
+
+ if (ast_bridge_interval_hook(bridge_channel->features, 0, ms > 0 ? ms : 1,
+ bridge_timeout, NULL, NULL, 0)) {
+ return;
+ }
+
+ ast_queue_frame(bridge_channel->chan, &ast_null_frame);
+}
+
int control_swap_channel_in_bridge(struct stasis_app_control *control, struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap)
{
int res;
@@ -969,6 +1194,10 @@ int control_swap_channel_in_bridge(struct stasis_app_control *control, struct as
ast_assert(stasis_app_get_bridge(control) == NULL);
control->bridge = bridge;
+
+ ast_channel_lock(chan);
+ set_interval_hook(chan);
+ ast_channel_unlock(chan);
}
return 0;
}
@@ -1011,7 +1240,7 @@ static int app_control_remove_channel_from_bridge(
return -1;
}
- ast_bridge_depart(chan);
+ depart_channel(control, chan);
return 0;
}
@@ -1132,83 +1361,110 @@ struct stasis_app *control_app(struct stasis_app_control *control)
return control->app;
}
-static void app_control_dial_destroy(void *data)
+struct control_dial_args {
+ unsigned int timeout;
+ char dialstring[0];
+};
+
+static struct control_dial_args *control_dial_args_alloc(const char *dialstring,
+ unsigned int timeout)
{
- struct ast_dial *dial = data;
+ struct control_dial_args *args;
+
+ args = ast_malloc(sizeof(*args) + strlen(dialstring) + 1);
+ if (!args) {
+ return NULL;
+ }
+
+ args->timeout = timeout;
+ /* Safe */
+ strcpy(args->dialstring, dialstring);
- ast_dial_join(dial);
- ast_dial_destroy(dial);
+ return args;
}
-static int app_control_remove_dial(struct stasis_app_control *control,
- struct ast_channel *chan, void *data)
+static void control_dial_args_destroy(void *data)
{
- if (ast_dial_state(control->dial) != AST_DIAL_RESULT_ANSWERED) {
- ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT);
- }
- control->dial = NULL;
- return 0;
+ struct control_dial_args *args = data;
+
+ ast_free(args);
}
-static void on_dial_state(struct ast_dial *dial)
+/*!
+ * \brief Set dial timeout on a channel to be dialed.
+ *
+ * \param chan The channel on which to set the dial timeout
+ * \param timeout The timeout in seconds
+ */
+static int set_timeout(struct ast_channel *chan, unsigned int timeout)
{
- enum ast_dial_result state;
- struct stasis_app_control *control;
- struct ast_channel *chan;
+ struct ast_datastore *datastore;
+ struct timeval *hangup_time;
- state = ast_dial_state(dial);
- control = ast_dial_get_user_data(dial);
+ hangup_time = ast_malloc(sizeof(struct timeval));
- switch (state) {
- case AST_DIAL_RESULT_ANSWERED:
- /* Need to steal the reference to the answered channel so that dial doesn't
- * try to hang it up when we destroy the dial structure.
- */
- chan = ast_dial_answered_steal(dial);
- ast_channel_unref(chan);
- /* Fall through intentionally */
- case AST_DIAL_RESULT_INVALID:
- case AST_DIAL_RESULT_FAILED:
- case AST_DIAL_RESULT_TIMEOUT:
- case AST_DIAL_RESULT_HANGUP:
- case AST_DIAL_RESULT_UNANSWERED:
- /* The dial has completed, so we need to break the Stasis loop so
- * that the channel's frames are handled in the proper place now.
- */
- stasis_app_send_command_async(control, app_control_remove_dial, dial, app_control_dial_destroy);
- break;
- case AST_DIAL_RESULT_TRYING:
- case AST_DIAL_RESULT_RINGING:
- case AST_DIAL_RESULT_PROGRESS:
- case AST_DIAL_RESULT_PROCEEDING:
- break;
+ datastore = ast_datastore_alloc(&timeout_datastore, NULL);
+ if (!datastore) {
+ return -1;
+ }
+ *hangup_time = ast_tvadd(ast_tvnow(), ast_samp2tv(timeout, 1));
+ datastore->data = hangup_time;
+
+ ast_channel_lock(chan);
+ ast_channel_datastore_add(chan, datastore);
+
+ if (ast_channel_is_bridged(chan)) {
+ set_interval_hook(chan);
}
+ ast_channel_unlock(chan);
+
+ return 0;
}
static int app_control_dial(struct stasis_app_control *control,
struct ast_channel *chan, void *data)
{
- struct ast_dial *dial = data;
+ struct control_dial_args *args = data;
+ int bridged;
- ast_dial_set_state_callback(dial, on_dial_state);
- /* The dial API gives the option of providing a caller channel, but for
- * Stasis, we really don't want to do that. The Dial API will take liberties such
- * as passing frames along to the calling channel (think ringing, progress, etc.).
- * This is not desirable in ARI applications since application writers should have
- * control over what does/does not get indicated to the calling channel
- */
- ast_dial_run(dial, NULL, 1);
- control->dial = dial;
+ ast_channel_lock(chan);
+ bridged = ast_channel_is_bridged(chan);
+ ast_channel_unlock(chan);
+
+ if (!bridged && add_to_dial_bridge(control, chan)) {
+ return -1;
+ }
+
+ if (args->timeout && set_timeout(chan, args->timeout)) {
+ return -1;
+ }
+
+ if (ast_call(chan, args->dialstring, 0)) {
+ return -1;
+ }
return 0;
}
-struct ast_dial *stasis_app_get_dial(struct stasis_app_control *control)
+int stasis_app_control_dial(struct stasis_app_control *control,
+ const char *dialstring, unsigned int timeout)
{
- return control->dial;
+ struct control_dial_args *args;
+
+ args = control_dial_args_alloc(dialstring, timeout);
+ if (!args) {
+ return -1;
+ }
+
+ return stasis_app_send_command_async(control, app_control_dial,
+ args, control_dial_args_destroy);
}
-int stasis_app_control_dial(struct stasis_app_control *control, struct ast_dial *dial)
+void stasis_app_control_shutdown(void)
{
- return stasis_app_send_command_async(control, app_control_dial, dial, NULL);
+ ast_mutex_lock(&dial_bridge_lock);
+ shutting_down = 1;
+ ao2_cleanup(dial_bridge);
+ dial_bridge = NULL;
+ ast_mutex_unlock(&dial_bridge_lock);
}