[Asterisk-code-review] ARI: Re-implement the ARI dial command, allowing for early b... (asterisk[master])

Anonymous Coward asteriskteam at digium.com
Tue May 31 12:39:54 CDT 2016


Anonymous Coward #1000019 has submitted this change and it was merged.

Change subject: ARI: Re-implement the ARI dial command, allowing for early bridging.
......................................................................


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
---
M include/asterisk/stasis_app.h
M res/ari/resource_channels.c
M res/res_stasis.c
M res/stasis/control.c
4 files changed, 480 insertions(+), 106 deletions(-)

Approvals:
  Mark Michelson: Looks good to me, approved
  George Joseph: Looks good to me, but someone else must approve
  Richard Mudgett: Looks good to me, but someone else must approve
  Anonymous Coward #1000019: Verified



diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h
index 0863f9f..a734615 100644
--- a/include/asterisk/stasis_app.h
+++ b/include/asterisk/stasis_app.h
@@ -673,6 +673,18 @@
 struct ast_bridge *stasis_app_bridge_create(const char *type, const char *name, const char *id);
 
 /*!
+ * \brief Create an invisible bridge of the specified type.
+ *
+ * \param type The type of bridge to be created
+ * \param name Optional name to give to the bridge
+ * \param id Optional Unique ID to give to the bridge
+ *
+ * \return New bridge.
+ * \return \c NULL on error.
+ */
+struct ast_bridge *stasis_app_bridge_create_invisible(const char *type, const char *name, const char *id);
+
+/*!
  * \brief Returns the bridge with the given id.
  * \param bridge_id Uniqueid of the bridge.
  *
@@ -855,20 +867,23 @@
  */
 int stasis_app_channel_set_internal(struct ast_channel *chan);
 
-struct ast_dial;
-
 /*!
  * \brief Dial a channel
  * \param control Control for \c res_stasis.
- * \param dial The ast_dial for the outbound channel
+ * \param dialstring The dialstring to pass to the channel driver
+ * \param timeout Optional timeout in milliseconds
  */
-int stasis_app_control_dial(struct stasis_app_control *control, struct ast_dial *dial);
+int stasis_app_control_dial(struct stasis_app_control *control,
+		const char *dialstring, unsigned int timeout);
 
 /*!
- * \brief Get dial structure on a control
+ * \brief Let Stasis app internals shut down
+ *
+ * This is called when res_stasis is unloaded. It ensures that
+ * the Stasis app internals can free any resources they may have
+ * allocated during the time that res_stasis was loaded.
  */
-struct ast_dial *stasis_app_get_dial(struct stasis_app_control *control);
-
+void stasis_app_control_shutdown(void);
 /*! @} */
 
 #endif /* _ASTERISK_STASIS_APP_H */
diff --git a/res/ari/resource_channels.c b/res/ari/resource_channels.c
index c838bc3..d0abcfd 100644
--- a/res/ari/resource_channels.c
+++ b/res/ari/resource_channels.c
@@ -48,6 +48,7 @@
 #include "asterisk/format_cache.h"
 #include "asterisk/core_local.h"
 #include "asterisk/dial.h"
+#include "asterisk/max_forwards.h"
 #include "resource_channels.h"
 
 #include <limits.h>
@@ -1500,6 +1501,67 @@
 	return NULL;
 }
 
+struct ast_datastore_info dialstring_info = {
+	.type = "ARI Dialstring",
+	.destroy = ast_free_ptr,
+};
+
+/*!
+ * \brief Save dialstring onto a channel datastore
+ *
+ * This will later be retrieved when it comes time to actually dial the channel
+ *
+ * \param chan The channel on which to save the dialstring
+ * \param dialstring The dialstring to save
+ * \retval 0 SUCCESS!
+ * \reval -1 Failure :(
+ */
+static int save_dialstring(struct ast_channel *chan, const char *dialstring)
+{
+	struct ast_datastore *datastore;
+
+	datastore = ast_datastore_alloc(&dialstring_info, NULL);
+	if (!datastore) {
+		return -1;
+	}
+
+	datastore->data = ast_strdup(dialstring);
+	if (!datastore->data) {
+		ast_datastore_free(datastore);
+		return -1;
+	}
+
+	ast_channel_lock(chan);
+	if (ast_channel_datastore_add(chan, datastore)) {
+		ast_channel_unlock(chan);
+		ast_datastore_free(datastore);
+		return -1;
+	}
+	ast_channel_unlock(chan);
+
+	return 0;
+}
+
+/*!
+ * \brief Retrieve the dialstring from the channel datastore
+ *
+ * \pre chan is locked
+ * \param chan Channel that was previously created in ARI
+ * \retval NULL Failed to find datastore
+ * \retval non-NULL The dialstring
+ */
+static char *restore_dialstring(struct ast_channel *chan)
+{
+	struct ast_datastore *datastore;
+
+	datastore = ast_channel_datastore_find(chan, &dialstring_info, NULL);
+	if (!datastore) {
+		return NULL;
+	}
+
+	return datastore->data;
+}
+
 void ast_ari_channels_create(struct ast_variable *headers,
 	struct ast_ari_channels_create_args *args,
 	struct ast_ari_response *response)
@@ -1559,6 +1621,12 @@
 		return;
 	}
 
+	if (save_dialstring(chan_data->chan, stuff)) {
+		ast_ari_response_alloc_failed(response);
+		chan_data_destroy(chan_data);
+		return;
+	}
+
 	snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(chan_data->chan));
 
 	if (ast_pthread_create_detached(&thread, NULL, ari_channel_thread, chan_data)) {
@@ -1577,8 +1645,8 @@
 {
 	RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
 	RAII_VAR(struct ast_channel *, caller, NULL, ast_channel_cleanup);
-	struct ast_channel *callee;
-	struct ast_dial *dial;
+	RAII_VAR(struct ast_channel *, callee, NULL, ast_channel_cleanup);
+	char *dialstring;
 
 	control = find_control(response, args->channel_id);
 	if (control == NULL) {
@@ -1595,43 +1663,64 @@
 		return;
 	}
 
-	if (ast_channel_state(callee) != AST_STATE_DOWN) {
-		ast_channel_unref(callee);
+	if (ast_channel_state(callee) != AST_STATE_DOWN
+		&& ast_channel_state(callee) != AST_STATE_RESERVED) {
 		ast_ari_response_error(response, 409, "Conflict",
 			"Channel is not in the 'Down' state");
 		return;
 	}
 
-	dial = ast_dial_create();
-	if (!dial) {
-		ast_channel_unref(callee);
-		ast_ari_response_alloc_failed(response);
-		return;
-	}
-
-	if (ast_dial_append_channel(dial, callee) < 0) {
-		ast_channel_unref(callee);
-		ast_dial_destroy(dial);
-		ast_ari_response_alloc_failed(response);
-		return;
-	}
-
-	/* From this point, we don't have to unref the callee channel on
-	 * failure paths because the dial owns the reference to the called
-	 * channel and will unref the channel for us
+	/* XXX This is straight up copied from main/dial.c. It's probably good
+	 * to separate this to some common method.
 	 */
-
-	if (ast_dial_prerun(dial, caller, NULL)) {
-		ast_dial_destroy(dial);
-		ast_ari_response_alloc_failed(response);
-		return;
+	if (caller) {
+		ast_channel_lock_both(caller, callee);
+	} else {
+		ast_channel_lock(callee);
 	}
 
-	ast_dial_set_user_data(dial, control);
-	ast_dial_set_global_timeout(dial, args->timeout * 1000);
+	dialstring = restore_dialstring(callee);
+	if (!dialstring) {
+		ast_channel_unlock(callee);
+		if (caller) {
+			ast_channel_unlock(caller);
+		}
+		ast_ari_response_error(response, 409, "Conflict",
+			"Dialing a channel not created by ARI");
+		return;
+	}
+	/* Make a copy of the dialstring just in case some jerk tries to hang up the
+	 * channel before we can actually dial
+	 */
+	dialstring = ast_strdupa(dialstring);
 
-	if (stasis_app_control_dial(control, dial)) {
-		ast_dial_destroy(dial);
+	ast_channel_stage_snapshot(callee);
+	if (caller) {
+		ast_channel_inherit_variables(caller, callee);
+		ast_channel_datastore_inherit(caller, callee);
+		ast_max_forwards_decrement(callee);
+
+		/* Copy over callerid information */
+		ast_party_redirecting_copy(ast_channel_redirecting(callee), ast_channel_redirecting(caller));
+
+		ast_channel_dialed(callee)->transit_network_select = ast_channel_dialed(caller)->transit_network_select;
+
+		ast_connected_line_copy_from_caller(ast_channel_connected(callee), ast_channel_caller(caller));
+
+		ast_channel_language_set(callee, ast_channel_language(caller));
+		ast_channel_req_accountcodes(callee, caller, AST_CHANNEL_REQUESTOR_BRIDGE_PEER);
+		if (ast_strlen_zero(ast_channel_musicclass(callee)))
+			ast_channel_musicclass_set(callee, ast_channel_musicclass(caller));
+
+		ast_channel_adsicpe_set(callee, ast_channel_adsicpe(caller));
+		ast_channel_transfercapability_set(callee, ast_channel_transfercapability(caller));
+		ast_channel_unlock(caller);
+	}
+
+	ast_channel_stage_snapshot_done(callee);
+	ast_channel_unlock(callee);
+
+	if (stasis_app_control_dial(control, dialstring, args->timeout)) {
 		ast_ari_response_alloc_failed(response);
 		return;
 	}
diff --git a/res/res_stasis.c b/res/res_stasis.c
index 346be56..e7e6bca 100644
--- a/res/res_stasis.c
+++ b/res/res_stasis.c
@@ -749,7 +749,7 @@
 	ao2_cleanup(control);
 }
 
-struct ast_bridge *stasis_app_bridge_create(const char *type, const char *name, const char *id)
+static struct ast_bridge *bridge_create_common(const char *type, const char *name, const char *id, int invisible)
 {
 	struct ast_bridge *bridge;
 	char *requested_type, *requested_types = ast_strdupa(S_OR(type, "mixing"));
@@ -757,6 +757,10 @@
 	int flags = AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM | AST_BRIDGE_FLAG_MERGE_INHIBIT_TO
 		| AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM | AST_BRIDGE_FLAG_SWAP_INHIBIT_TO
 		| AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY;
+
+	if (invisible) {
+		flags |= AST_BRIDGE_FLAG_INVISIBLE;
+	}
 
 	while ((requested_type = strsep(&requested_types, ","))) {
 		requested_type = ast_strip(requested_type);
@@ -787,6 +791,16 @@
 		}
 	}
 	return bridge;
+}
+
+struct ast_bridge *stasis_app_bridge_create(const char *type, const char *name, const char *id)
+{
+	return bridge_create_common(type, name, id, 0);
+}
+
+struct ast_bridge *stasis_app_bridge_create_invisible(const char *type, const char *name, const char *id)
+{
+	return bridge_create_common(type, name, id, 1);
 }
 
 void stasis_app_bridge_destroy(const char *bridge_id)
@@ -1287,7 +1301,6 @@
 		int r;
 		int command_count;
 		RAII_VAR(struct ast_bridge *, last_bridge, NULL, ao2_cleanup);
-		struct ast_dial *dial;
 
 		/* Check to see if a bridge absorbed our hangup frame */
 		if (ast_check_hangup_locked(chan)) {
@@ -1297,7 +1310,6 @@
 
 		last_bridge = bridge;
 		bridge = ao2_bump(stasis_app_get_bridge(control));
-		dial = stasis_app_get_dial(control);
 
 		if (bridge != last_bridge) {
 			app_unsubscribe_bridge(app, last_bridge);
@@ -1306,7 +1318,7 @@
 			}
 		}
 
-		if (bridge || dial) {
+		if (bridge) {
 			/* Bridge/dial is handling channel frames */
 			control_wait(control);
 			control_dispatch_all(control, chan);
@@ -1951,6 +1963,8 @@
 	ao2_cleanup(app_bridges_playback);
 	app_bridges_playback = NULL;
 
+	stasis_app_control_shutdown();
+
 	STASIS_MESSAGE_TYPE_CLEANUP(end_message_type);
 	STASIS_MESSAGE_TYPE_CLEANUP(start_message_type);
 
diff --git a/res/stasis/control.c b/res/stasis/control.c
index aa6866a..b255477 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"
@@ -42,6 +43,11 @@
 #include "asterisk/app.h"
 
 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;
@@ -77,10 +83,6 @@
 	 * The app for which this control was created
 	 */
 	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.
 	 */
@@ -825,6 +827,128 @@
 	}
 }
 
+/*!
+ * \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 @@
 	ast_debug(3, "%s: Channel departing bridge\n",
 		ast_channel_uniqueid(chan));
 
-	ast_bridge_depart(chan);
+	depart_channel(control, chan);
 
 	return 0;
 }
@@ -901,6 +1025,107 @@
 
 	ast_debug(3, "  reason: %s\n",
 		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)
@@ -969,6 +1194,10 @@
 
 		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 @@
 		return -1;
 	}
 
-	ast_bridge_depart(chan);
+	depart_channel(control, chan);
 	return 0;
 }
 
@@ -1132,83 +1361,110 @@
 	return control->app;
 }
 
-static void app_control_dial_destroy(void *data)
-{
-	struct ast_dial *dial = data;
+struct control_dial_args {
+	unsigned int timeout;
+	char dialstring[0];
+};
 
-	ast_dial_join(dial);
-	ast_dial_destroy(dial);
+static struct control_dial_args *control_dial_args_alloc(const char *dialstring,
+	unsigned int timeout)
+{
+	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);
+
+	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);
+	struct control_dial_args *args = data;
+
+	ast_free(args);
+}
+
+/*!
+ * \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)
+{
+	struct ast_datastore *datastore;
+	struct timeval *hangup_time;
+
+	hangup_time = ast_malloc(sizeof(struct timeval));
+
+	datastore = ast_datastore_alloc(&timeout_datastore, NULL);
+	if (!datastore) {
+		return -1;
 	}
-	control->dial = NULL;
+	*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 void on_dial_state(struct ast_dial *dial)
-{
-	enum ast_dial_result state;
-	struct stasis_app_control *control;
-	struct ast_channel *chan;
-
-	state = ast_dial_state(dial);
-	control = ast_dial_get_user_data(dial);
-
-	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;
-	}
 }
 
 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);
 }

-- 
To view, visit https://gerrit.asterisk.org/2790
To unsubscribe, visit https://gerrit.asterisk.org/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I7750359ddf45fcd45eaec749c5b3822de4a8ddbb
Gerrit-PatchSet: 10
Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-Owner: Mark Michelson <mmichelson at digium.com>
Gerrit-Reviewer: Anonymous Coward #1000019
Gerrit-Reviewer: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Joshua Colp <jcolp at digium.com>
Gerrit-Reviewer: Mark Michelson <mmichelson at digium.com>
Gerrit-Reviewer: Matt Jordan <mjordan at digium.com>
Gerrit-Reviewer: Richard Mudgett <rmudgett at digium.com>



More information about the asterisk-code-review mailing list