diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h index 0863f9f98c24f6cb23fa79f009adf85a445d109b..a73461547e0f977356744361dee518f435606419 100644 --- a/include/asterisk/stasis_app.h +++ b/include/asterisk/stasis_app.h @@ -672,6 +672,18 @@ int stasis_app_control_queue_control(struct stasis_app_control *control, */ 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_unreal_set_internal(struct ast_channel *chan); */ 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 b42581c8463511891aaf13ec43a03c88e23fd4cf..0f18b2dc1d5117e18d2425b5547ed2f63419a8db 100644 --- a/res/ari/resource_channels.c +++ b/res/ari/resource_channels.c @@ -48,6 +48,7 @@ ASTERISK_REGISTER_FILE() #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> @@ -1503,6 +1504,67 @@ static void *ari_channel_thread(void *data) 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) @@ -1562,6 +1624,12 @@ void ast_ari_channels_create(struct ast_variable *headers, 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)) { @@ -1580,8 +1648,8 @@ void ast_ari_channels_dial(struct ast_variable *headers, { 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) { @@ -1598,43 +1666,64 @@ void ast_ari_channels_dial(struct ast_variable *headers, 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; + /* XXX This is straight up copied from main/dial.c. It's probably good + * to separate this to some common method. + */ + if (caller) { + ast_channel_lock_both(caller, callee); + } else { + ast_channel_lock(callee); } - if (ast_dial_append_channel(dial, callee) < 0) { - ast_channel_unref(callee); - ast_dial_destroy(dial); - ast_ari_response_alloc_failed(response); + 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; } - - /* 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 + /* 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 (ast_dial_prerun(dial, caller, NULL)) { - ast_dial_destroy(dial); - ast_ari_response_alloc_failed(response); - return; + 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_dial_set_user_data(dial, control); - ast_dial_set_global_timeout(dial, args->timeout * 1000); + ast_channel_stage_snapshot_done(callee); + ast_channel_unlock(callee); - if (stasis_app_control_dial(control, dial)) { - ast_dial_destroy(dial); + 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 346be563c1ae9d7a9fb9c4dd1b0a2462eeedd81b..e7e6bcaa3179903992c58b8b2a11253b9782f197 100644 --- a/res/res_stasis.c +++ b/res/res_stasis.c @@ -749,7 +749,7 @@ static void control_unlink(struct stasis_app_control *control) 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")); @@ -758,6 +758,10 @@ struct ast_bridge *stasis_app_bridge_create(const char *type, const char *name, | 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); @@ -789,6 +793,16 @@ struct ast_bridge *stasis_app_bridge_create(const char *type, const char *name, 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) { struct ast_bridge *bridge = stasis_app_bridge_find_by_id(bridge_id); @@ -1287,7 +1301,6 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc, 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 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc, 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 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc, } } - if (bridge || dial) { + if (bridge) { /* Bridge/dial is handling channel frames */ control_wait(control); control_dispatch_all(control, chan); @@ -1951,6 +1963,8 @@ static int unload_module(void) 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 aa6866aee6d3a63190f01a558ffe8128fd7e97e5..b255477bf730e374c3920572419520329e26cc55 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 */ @@ -77,10 +83,6 @@ struct stasis_app_control { * 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 @@ 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); }