static int find_route( struct stasis_message_router *router, struct stasis_message *message, struct stasis_message_route *route_out) { struct stasis_message_route *route = NULL; struct stasis_message_type *type = stasis_message_type(message); SCOPED_AO2LOCK(lock, router); ast_assert(route_out != NULL); if (type == stasis_cache_update_type()) { /* Find a cache route */ struct stasis_cache_update *update = stasis_message_data(message); route = route_table_find(&router->cache_routes, update->type); } if (route == NULL) { /* Find a regular route */ route = route_table_find(&router->routes, type); } if (route == NULL && router->default_route.callback) { /* Maybe the default route, then? */ route = &router->default_route; } if (!route) { return -1; } *route_out = *route; return 0; }
/*! * \brief Explicitly shutdown a session. * * \details An explicit shutdown is necessary, since the \ref stasis_app has a reference * to this session. We also need to be sure to null out the \c ws_session field, * since the websocket is about to go away. * * \internal * * \param session Event session object (\ref event_session). */ static void event_session_shutdown(struct event_session *session) { struct ao2_iterator i; char *app; int j; SCOPED_AO2LOCK(lock, session); /* Clean up the websocket_apps container */ if (session->websocket_apps) { i = ao2_iterator_init(session->websocket_apps, 0); while ((app = ao2_iterator_next(&i))) { stasis_app_unregister(app); ao2_cleanup(app); } ao2_iterator_destroy(&i); ao2_cleanup(session->websocket_apps); session->websocket_apps = NULL; } /* Clean up the message_queue container */ for (j = 0; j < AST_VECTOR_SIZE(&session->message_queue); j++) { struct ast_json *msg = AST_VECTOR_GET(&session->message_queue, j); ast_json_unref(msg); } AST_VECTOR_FREE(&session->message_queue); /* Remove the handle to the underlying websocket session */ session->ws_session = NULL; }
int app_subscribe_channel(struct stasis_app *app, struct ast_channel *chan) { int res; if (!app || !chan) { return -1; } else { RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup); SCOPED_AO2LOCK(lock, app->forwards); forwards = ao2_find(app->forwards, ast_channel_uniqueid(chan), OBJ_SEARCH_KEY | OBJ_NOLOCK); if (!forwards) { /* Forwards not found, create one */ forwards = forwards_create_channel(app, chan); if (!forwards) { return -1; } res = ao2_link_flags(app->forwards, forwards, OBJ_NOLOCK); if (!res) { return -1; } } ++forwards->interested; ast_debug(3, "Channel '%s' is %d interested in %s\n", ast_channel_uniqueid(chan), forwards->interested, app->name); return 0; } }
static void app_control_register_rule( const struct stasis_app_control *control, struct app_control_rules *list, struct stasis_app_control_rule *obj) { SCOPED_AO2LOCK(lock, control->command_queue); AST_LIST_INSERT_TAIL(list, obj, next); }
static int unsubscribe(struct stasis_app *app, const char *kind, const char *id, int terminate) { RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup); SCOPED_AO2LOCK(lock, app->forwards); forwards = ao2_find(app->forwards, id, OBJ_SEARCH_KEY | OBJ_NOLOCK); if (!forwards) { ast_debug(3, "App '%s' not subscribed to %s '%s'\n", app->name, kind, id); return -1; } forwards->interested--; ast_debug(3, "%s '%s': is %d interested in %s\n", kind, id, forwards->interested, app->name); if (forwards->interested == 0 || terminate) { /* No one is interested any more; unsubscribe */ ast_debug(3, "%s '%s' unsubscribed from %s\n", kind, id, app->name); forwards_unsubscribe(forwards); ao2_find(app->forwards, forwards, OBJ_POINTER | OBJ_NOLOCK | OBJ_UNLINK | OBJ_NODATA); if (!strcmp(kind, "endpoint")) { messaging_app_unsubscribe_endpoint(app->name, id); } } return 0; }
int app_subscribe_endpoint(struct stasis_app *app, struct ast_endpoint *endpoint) { if (!app || !endpoint) { return -1; } else { RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup); SCOPED_AO2LOCK(lock, app->forwards); forwards = ao2_find(app->forwards, ast_endpoint_get_id(endpoint), OBJ_SEARCH_KEY | OBJ_NOLOCK); if (!forwards) { /* Forwards not found, create one */ forwards = forwards_create_endpoint(app, endpoint); if (!forwards) { return -1; } ao2_link_flags(app->forwards, forwards, OBJ_NOLOCK); /* Subscribe for messages */ messaging_app_subscribe_endpoint(app->name, endpoint, &message_received_handler, app); } ++forwards->interested; ast_debug(3, "Endpoint '%s' is %d interested in %s\n", ast_endpoint_get_id(endpoint), forwards->interested, app->name); return 0; } }
int app_subscribe_bridge(struct stasis_app *app, struct ast_bridge *bridge) { if (!app || !bridge) { return -1; } else { RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup); SCOPED_AO2LOCK(lock, app->forwards); forwards = ao2_find(app->forwards, bridge->uniqueid, OBJ_SEARCH_KEY | OBJ_NOLOCK); if (!forwards) { /* Forwards not found, create one */ forwards = forwards_create_bridge(app, bridge); if (!forwards) { return -1; } ao2_link_flags(app->forwards, forwards, OBJ_NOLOCK); } ++forwards->interested; ast_debug(3, "Bridge '%s' is %d interested in %s\n", bridge->uniqueid, forwards->interested, app->name); return 0; } }
void app_update(struct stasis_app *app, stasis_app_cb handler, void *data) { SCOPED_AO2LOCK(lock, app); if (app->handler) { RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref); ast_verb(1, "Replacing Stasis app '%s'\n", app->name); msg = ast_json_pack("{s: s, s: s}", "type", "ApplicationReplaced", "application", app->name); if (msg) { app_send(app, msg); } } else { ast_verb(1, "Activating Stasis app '%s'\n", app->name); } app->handler = handler; ao2_cleanup(app->data); if (data) { ao2_ref(data, +1); } app->data = data; }
struct stasis_topic *stasis_topic_pool_get_topic(struct stasis_topic_pool *pool, const char *topic_name) { RAII_VAR(struct topic_pool_entry *, topic_pool_entry, NULL, ao2_cleanup); SCOPED_AO2LOCK(topic_container_lock, pool->pool_container); topic_pool_entry = ao2_find(pool->pool_container, topic_name, OBJ_SEARCH_KEY | OBJ_NOLOCK); if (topic_pool_entry) { return topic_pool_entry->topic; } topic_pool_entry = topic_pool_entry_alloc(); if (!topic_pool_entry) { return NULL; } topic_pool_entry->topic = stasis_topic_create(topic_name); if (!topic_pool_entry->topic) { return NULL; } topic_pool_entry->forward = stasis_forward_all(topic_pool_entry->topic, pool->pool_topic); if (!topic_pool_entry->forward) { return NULL; } if (!ao2_link_flags(pool->pool_container, topic_pool_entry, OBJ_NOLOCK)) { return NULL; } return topic_pool_entry->topic; }
static struct wait_bridge_wrapper *get_wait_bridge_wrapper(const char *bridge_name) { struct wait_bridge_wrapper * wrapper; struct ast_bridge *bridge = NULL; SCOPED_AO2LOCK(lock, wait_bridge_wrappers); if ((wrapper = wait_bridge_wrapper_find_by_name(bridge_name))) { return wrapper; } /* * Holding bridges can allow local channel move/swap * optimization to the bridge. However, we cannot allow it for * this holding bridge because the call will lose the channel * roles and dialplan location as a result. */ bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_HOLDING, AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM | AST_BRIDGE_FLAG_SWAP_INHIBIT_TO | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM | AST_BRIDGE_FLAG_TRANSFER_PROHIBITED, APP_NAME, bridge_name, NULL); if (!bridge) { return NULL; } /* The bridge reference is unconditionally passed. */ return wait_bridge_wrapper_alloc(bridge_name, bridge); }
/*! * \brief Send a message to the given application. * \param app App to send the message to. * \param message Message to send. */ void app_send(struct stasis_app *app, struct ast_json *message) { stasis_app_cb handler; char eid[20]; RAII_VAR(void *, data, NULL, ao2_cleanup); if (ast_json_object_set(message, "asterisk_id", ast_json_string_create( ast_eid_to_str(eid, sizeof(eid), &ast_eid_default)))) { ast_log(AST_LOG_WARNING, "Failed to append EID to outgoing event %s\n", ast_json_string_get(ast_json_object_get(message, "type"))); } /* Copy off mutable state with lock held */ { SCOPED_AO2LOCK(lock, app); handler = app->handler; if (app->data) { ao2_ref(app->data, +1); data = app->data; } /* Name is immutable; no need to copy */ } if (!handler) { ast_verb(3, "Inactive Stasis app '%s' missed message\n", app->name); return; } handler(data, app->name, message); }
static void consumer_exec(void *data, struct stasis_subscription *sub, struct stasis_message *message) { struct consumer *consumer = data; struct stasis_cache_update *cache_update = stasis_message_data(message); struct ast_device_state_message *device_state; if (!cache_update->new_snapshot) { return; } device_state = stasis_message_data(cache_update->new_snapshot); if (strcmp(device_state->device, UNIT_TEST_DEVICE_IDENTIFIER)) { /* not a device state we're interested in */ return; } { SCOPED_AO2LOCK(lock, consumer); ++consumer->event_count; if (device_state->eid) { consumer->state = device_state->state; if (consumer->sig_on_non_aggregate_state) { consumer->sig_on_non_aggregate_state = 0; consumer->already_out = 1; ast_cond_signal(&consumer->out); } } else { consumer->aggregate_state = device_state->state; consumer->already_out = 1; ast_cond_signal(&consumer->out); } } }
static void bridge_after_cb(struct ast_channel *chan, void *data) { struct stasis_app_control *control = data; SCOPED_AO2LOCK(lock, control); struct ast_bridge_channel *bridge_channel; ast_debug(3, "%s, %s: Channel leaving bridge\n", ast_channel_uniqueid(chan), control->bridge->uniqueid); ast_assert(chan == control->channel); /* Restore the channel's PBX */ ast_channel_pbx_set(control->channel, control->pbx); control->pbx = NULL; /* No longer in the bridge */ control->bridge = NULL; /* Get the bridge channel so we don't depart from the wrong bridge */ ast_channel_lock(chan); bridge_channel = ast_channel_get_bridge_channel(chan); ast_channel_unlock(chan); /* Depart this channel from the bridge using the command queue if possible */ if (stasis_app_send_command_async(control, bridge_channel_depart, bridge_channel)) { ao2_cleanup(bridge_channel); } }
void app_deactivate(struct stasis_app *app) { SCOPED_AO2LOCK(lock, app); ast_verb(1, "Deactivating Stasis app '%s'\n", app->name); app->handler = NULL; ao2_cleanup(app->data); app->data = NULL; }
int ast_threadpool_push(struct ast_threadpool *pool, int (*task)(void *data), void *data) { SCOPED_AO2LOCK(lock, pool); if (!pool->shutting_down) { return ast_taskprocessor_push(pool->tps, task, data); } return -1; }
static void recording_set_state(struct stasis_app_recording *recording, enum stasis_app_recording_state state, const char *cause) { SCOPED_AO2LOCK(lock, recording); recording->state = state; recording_publish(recording, cause); }
struct ast_bridge *stasis_app_get_bridge(struct stasis_app_control *control) { if (!control) { return NULL; } else { SCOPED_AO2LOCK(lock, control); return control->bridge; } }
void app_shutdown(struct stasis_app *app) { SCOPED_AO2LOCK(lock, app); ast_assert(app_is_finished(app)); stasis_message_router_unsubscribe(app->router); app->router = NULL; stasis_message_router_unsubscribe(app->bridge_router); app->bridge_router = NULL; }
void app_set_debug(struct stasis_app *app, int debug) { if (!app) { return; } { SCOPED_AO2LOCK(lock, app); app->debug = debug; } }
int stasis_subscription_is_done(struct stasis_subscription *subscription) { if (subscription) { SCOPED_AO2LOCK(lock, subscription); return subscription->final_message_rxed; } /* Null subscription is about as done as you can get */ return 1; }
void stasis_subscription_join(struct stasis_subscription *subscription) { if (subscription) { SCOPED_AO2LOCK(lock, subscription); /* Wait until the processed flag has been set */ while (!subscription->final_message_processed) { ast_cond_wait(&subscription->join_cond, ao2_object_get_lockaddr(subscription)); } } }
static void internal_bridge_after_cb(struct ast_channel *chan, void *data, enum ast_bridge_after_cb_reason reason) { struct stasis_app_control *control = data; SCOPED_AO2LOCK(lock, control); struct ast_bridge_channel *bridge_channel; ast_debug(3, "%s, %s: %s\n", ast_channel_uniqueid(chan), control->bridge ? control->bridge->uniqueid : "unknown", ast_bridge_after_cb_reason_string(reason)); if (reason == AST_BRIDGE_AFTER_CB_REASON_IMPART_FAILED) { /* The impart actually failed so control->bridge isn't valid. */ control->bridge = NULL; } ast_assert(chan == control->channel); /* Restore the channel's PBX */ ast_channel_pbx_set(control->channel, control->pbx); control->pbx = NULL; if (control->bridge) { app_unsubscribe_bridge(control->app, control->bridge); /* No longer in the bridge */ control->bridge = NULL; /* Get the bridge channel so we don't depart from the wrong bridge */ ast_channel_lock(chan); bridge_channel = ast_channel_get_bridge_channel(chan); ast_channel_unlock(chan); /* Depart this channel from the bridge using the command queue if possible */ stasis_app_send_command_async(control, bridge_channel_depart, bridge_channel, __ao2_cleanup); } if (stasis_app_channel_is_stasis_end_published(chan)) { /* The channel has had a StasisEnd published on it, but until now had remained in * the bridging system. This means that the channel moved from a Stasis bridge to a * non-Stasis bridge and is now exiting the bridging system. Because of this, the * channel needs to exit the Stasis application and go to wherever the non-Stasis * bridge has directed it to go. If the non-Stasis bridge has not set up an after * bridge destination, then the channel should be hung up. */ int hangup_flag; hangup_flag = ast_bridge_setup_after_goto(chan) ? AST_SOFTHANGUP_DEV : AST_SOFTHANGUP_ASYNCGOTO; ast_channel_lock(chan); ast_softhangup_nolock(chan, hangup_flag); ast_channel_unlock(chan); } }
/*! * \brief Invoke the subscription's callback. * \param sub Subscription to invoke. * \param topic Topic message was published to. * \param message Message to send. */ static void subscription_invoke(struct stasis_subscription *sub, struct stasis_message *message) { /* Notify that the final message has been received */ if (stasis_subscription_final_message(sub, message)) { SCOPED_AO2LOCK(lock, sub); sub->final_message_rxed = 1; ast_cond_signal(&sub->join_cond); } /* Since sub is mostly immutable, no need to lock sub */ sub->callback(sub->data, sub, message); /* Notify that the final message has been processed */ if (stasis_subscription_final_message(sub, message)) { SCOPED_AO2LOCK(lock, sub); sub->final_message_processed = 1; ast_cond_signal(&sub->join_cond); } }
static int topic_remove_subscription(struct stasis_topic *topic, struct stasis_subscription *sub) { size_t idx; SCOPED_AO2LOCK(lock_topic, topic); for (idx = 0; idx < AST_VECTOR_SIZE(&topic->upstream_topics); ++idx) { topic_remove_subscription( AST_VECTOR_GET(&topic->upstream_topics, idx), sub); } return AST_VECTOR_REMOVE_ELEM_UNORDERED(&topic->subscribers, sub, AST_VECTOR_ELEM_CLEANUP_NOOP); }
/*! * \brief Taskprocessor listener emptied callback * * The threadpool queues a task to let the threadpool listener know that * the threadpool no longer contains any tasks. * \param listener The taskprocessor listener. The threadpool is the listener's private data. */ static void threadpool_tps_emptied(struct ast_taskprocessor_listener *listener) { struct ast_threadpool *pool = ast_taskprocessor_listener_get_user_data(listener); SCOPED_AO2LOCK(lock, pool); if (pool->shutting_down) { return; } if (pool->listener && pool->listener->callbacks->emptied) { ast_taskprocessor_push(pool->control_tps, queued_emptied, pool); } }
static void threadpool_idle_thread_dead(struct ast_threadpool *pool, struct worker_thread *worker) { struct thread_worker_pair *pair; SCOPED_AO2LOCK(lock, pool); if (pool->shutting_down) { return; } pair = thread_worker_pair_alloc(pool, worker); if (!pair) { return; } ast_taskprocessor_push(pool->control_tps, queued_idle_thread_dead, pair); }
static void app_control_unregister_rule( const struct stasis_app_control *control, struct app_control_rules *list, struct stasis_app_control_rule *obj) { struct stasis_app_control_rule *rule; SCOPED_AO2LOCK(lock, control->command_queue); AST_RWLIST_TRAVERSE_SAFE_BEGIN(list, rule, next) { if (rule == obj) { AST_RWLIST_REMOVE_CURRENT(next); break; } } AST_RWLIST_TRAVERSE_SAFE_END; }
void ast_threadpool_set_size(struct ast_threadpool *pool, unsigned int size) { struct set_size_data *ssd; SCOPED_AO2LOCK(lock, pool); if (pool->shutting_down) { return; } ssd = set_size_data_alloc(pool, size); if (!ssd) { return; } ast_taskprocessor_push(pool->control_tps, queued_set_size, ssd); }
int stasis_subscription_is_subscribed(const struct stasis_subscription *sub) { if (sub) { size_t i; struct stasis_topic *topic = sub->topic; SCOPED_AO2LOCK(lock_topic, topic); for (i = 0; i < AST_VECTOR_SIZE(&topic->subscribers); ++i) { if (AST_VECTOR_GET(&topic->subscribers, i) == sub) { return 1; } } } return 0; }
/*! * \brief Explicitly shutdown a session. * * An explicit shutdown is necessary, since stasis-app has a reference to this * session. We also need to be sure to null out the \c ws_session field, since * the websocket is about to go away. * * \param session Session info struct. */ static void session_shutdown(struct event_session *session) { struct ao2_iterator i; char *app; SCOPED_AO2LOCK(lock, session); i = ao2_iterator_init(session->websocket_apps, 0); while ((app = ao2_iterator_next(&i))) { stasis_app_unregister(app); ao2_cleanup(app); } ao2_iterator_destroy(&i); ao2_cleanup(session->websocket_apps); session->websocket_apps = NULL; session->ws_session = NULL; }