static void caching_topic_exec(void *data, struct stasis_subscription *sub, struct stasis_message *message) { RAII_VAR(struct stasis_caching_topic *, caching_topic_needs_unref, NULL, ao2_cleanup); struct stasis_caching_topic *caching_topic = data; const char *id = NULL; ast_assert(caching_topic != NULL); ast_assert(caching_topic->topic != NULL); ast_assert(caching_topic->cache != NULL); ast_assert(caching_topic->cache->id_fn != NULL); if (stasis_subscription_final_message(sub, message)) { caching_topic_needs_unref = caching_topic; } /* Handle cache clear event */ if (stasis_cache_clear_type() == stasis_message_type(message)) { RAII_VAR(struct stasis_message *, old_snapshot, NULL, ao2_cleanup); RAII_VAR(struct stasis_message *, update, NULL, ao2_cleanup); struct stasis_message *clear_msg = stasis_message_data(message); const char *clear_id = caching_topic->cache->id_fn(clear_msg); struct stasis_message_type *clear_type = stasis_message_type(clear_msg); ast_assert(clear_type != NULL); if (clear_id) { old_snapshot = cache_put(caching_topic->cache, clear_type, clear_id, NULL); if (old_snapshot) { update = update_create(old_snapshot, NULL); stasis_publish(caching_topic->topic, update); return; } ast_log(LOG_ERROR, "Attempting to remove an item from the %s cache that isn't there: %s %s\n", stasis_topic_name(caching_topic->topic), stasis_message_type_name(clear_type), clear_id); return; } } id = caching_topic->cache->id_fn(message); if (id == NULL) { /* Object isn't cached; discard */ } else { /* Update the cache */ RAII_VAR(struct stasis_message *, old_snapshot, NULL, ao2_cleanup); RAII_VAR(struct stasis_message *, update, NULL, ao2_cleanup); old_snapshot = cache_put(caching_topic->cache, stasis_message_type(message), id, message); update = update_create(old_snapshot, message); if (update == NULL) { return; } stasis_publish(caching_topic->topic, update); } }
static void consumer_finalize(void *data, struct stasis_subscription *sub, struct stasis_message *message) { struct consumer *consumer = data; if (stasis_subscription_final_message(sub, message)) { ao2_cleanup(consumer); } }
static void bridge_subscription_change_handler(void *data, struct stasis_subscription *sub, struct stasis_message *message) { struct stasis_app *app = data; if (stasis_subscription_final_message(sub, message)) { ao2_cleanup(app); } }
/*! * \brief Router callback for any message that doesn't otherwise have a route. * \param data Data pointer given when added to router. * \param sub This subscription. * \param topic The topic the message was posted to. This is not necessarily the * topic you subscribed to, since messages may be forwarded between * topics. * \param message The message itself. */ static void default_route(void *data, struct stasis_subscription *sub, struct stasis_message *message) { if (stasis_subscription_final_message(sub, message)) { /* Much like with the regular subscription, you may need to * perform some cleanup when done with a message router. You * can look for the final message in the default route. */ return; } }
/*! * \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 void router_dispatch(void *data, struct stasis_subscription *sub, struct stasis_message *message) { struct stasis_message_router *router = data; struct stasis_message_route route; if (find_route(router, message, &route) == 0) { route.callback(route.data, sub, message); } if (stasis_subscription_final_message(sub, message)) { ao2_cleanup(router); } }
static void parker_update_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { if (stasis_subscription_final_message(sub, message)) { struct parked_subscription_data *ps_data = data; ao2_cleanup(ps_data->transfer_data); ps_data->transfer_data = NULL; ast_free(data); return; } if (stasis_message_type(message) == ast_parked_call_type()) { struct ast_parked_call_payload *parked_call_message = stasis_message_data(message); parker_parked_call_message_response(parked_call_message, data, sub); } }
static void mwi_stasis_cb(void *userdata, struct stasis_subscription *sub, struct stasis_message *msg) { struct mwi_subscription *mwi_sub = userdata; if (stasis_subscription_final_message(sub, msg)) { if (ast_sip_push_task(NULL, serialized_cleanup, ao2_bump(mwi_sub))) { ao2_ref(mwi_sub, -1); } return; } if (ast_mwi_state_type() == stasis_message_type(msg)) { send_notify(mwi_sub, NULL, 0); } }
static void mwi_stasis_cb(void *userdata, struct stasis_subscription *sub, struct stasis_message *msg) { struct mwi_subscription *mwi_sub = userdata; if (stasis_subscription_final_message(sub, msg)) { ao2_ref(mwi_sub, +1); ast_sip_push_task(NULL, serialized_cleanup, mwi_sub); return; } if (ast_mwi_state_type() == stasis_message_type(msg)) { struct ast_taskprocessor *serializer = mwi_sub->is_solicited ? ast_sip_subscription_get_serializer(mwi_sub->sip_sub) : NULL; ao2_ref(mwi_sub, +1); ast_sip_push_task(serializer, serialized_notify, mwi_sub); } }
static void sub_default_handler(void *data, struct stasis_subscription *sub, struct stasis_message *message) { struct stasis_app *app = data; RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); if (stasis_subscription_final_message(sub, message)) { ao2_cleanup(app); } if (stasis_message_type(message) == ast_channel_dial_type()) { call_forwarded_handler(app, message); } /* By default, send any message that has a JSON representation */ json = stasis_message_to_json(message, stasis_app_get_sanitizer()); if (!json) { return; } app_send(app, json); }
/*! * \brief Subscription callback for all channel messages. * \param data Data pointer given when creating the subscription. * \param sub This subscription. * \param topic The topic the message was posted to. This is not necessarily the * topic you subscribed to, since messages may be forwarded between * topics. * \param message The message itself. */ static void statsmaker(void *data, struct stasis_subscription *sub, struct stasis_message *message) { RAII_VAR(struct ast_str *, metric, NULL, ast_free); if (stasis_subscription_final_message(sub, message)) { /* Normally, data points to an object that must be cleaned up. * The final message is an unsubscribe notification that's * guaranteed to be the last message this subscription receives. * This would be a safe place to kick off any needed cleanup. */ return; } /* For no good reason, count message types */ metric = ast_str_create(80); if (metric) { ast_str_set(&metric, 0, "stasis.message.%s", stasis_message_type_name(stasis_message_type(message))); ast_statsd_log(ast_str_buffer(metric), AST_STATSD_METER, 1); } }
static struct timespec make_deadline(int timeout_millis) { struct timeval start = ast_tvnow(); struct timeval delta = { .tv_sec = timeout_millis / 1000, .tv_usec = (timeout_millis % 1000) * 1000, }; struct timeval deadline_tv = ast_tvadd(start, delta); struct timespec deadline = { .tv_sec = deadline_tv.tv_sec, .tv_nsec = 1000 * deadline_tv.tv_usec, }; return deadline; } struct stasis_message_sink *stasis_message_sink_create(void) { RAII_VAR(struct stasis_message_sink *, sink, NULL, ao2_cleanup); sink = ao2_alloc(sizeof(*sink), stasis_message_sink_dtor); if (!sink) { return NULL; } ast_mutex_init(&sink->lock); ast_cond_init(&sink->cond, NULL); sink->max_messages = 4; sink->messages = ast_malloc(sizeof(*sink->messages) * sink->max_messages); if (!sink->messages) { return NULL; } ao2_ref(sink, +1); return sink; } /*! * \brief Implementation of the stasis_message_sink_cb() callback. * * Why the roundabout way of exposing this via stasis_message_sink_cb()? Well, * it has to do with how we load modules. * * Modules have their own metadata compiled into them in the module info block * at the end of the file. This includes dependency information in the * \c nonoptreq field. * * Asterisk loads the module, inspects the field, then loads any needed * dependencies. This works because Asterisk passes \c RTLD_LAZY to the initial * dlopen(), which defers binding function references until they are called. * * But when you take the address of a function, that function needs to be * available at load time. So if some module used the address of * message_sink_cb() directly, and \c res_stasis_test.so wasn't loaded yet, then * that module would fail to load. * * The stasis_message_sink_cb() function gives us a layer of indirection so that * the initial lazy binding will still work as expected. */ static void message_sink_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { struct stasis_message_sink *sink = data; SCOPED_MUTEX(lock, &sink->lock); if (stasis_subscription_final_message(sub, message)) { sink->is_done = 1; ast_cond_signal(&sink->cond); return; } if (stasis_subscription_change_type() == stasis_message_type(message)) { /* Ignore subscription changes */ return; } if (sink->num_messages == sink->max_messages) { size_t new_max_messages = sink->max_messages * 2; struct stasis_message **new_messages = ast_realloc( sink->messages, sizeof(*new_messages) * new_max_messages); if (!new_messages) { return; } sink->max_messages = new_max_messages; sink->messages = new_messages; } ao2_ref(message, +1); sink->messages[sink->num_messages++] = message; ast_cond_signal(&sink->cond); } stasis_subscription_cb stasis_message_sink_cb(void) { return message_sink_cb; } int stasis_message_sink_wait_for_count(struct stasis_message_sink *sink, int num_messages, int timeout_millis) { struct timespec deadline = make_deadline(timeout_millis); SCOPED_MUTEX(lock, &sink->lock); while (sink->num_messages < num_messages) { int r = ast_cond_timedwait(&sink->cond, &sink->lock, &deadline); if (r == ETIMEDOUT) { break; } if (r != 0) { ast_log(LOG_ERROR, "Unexpected condition error: %s\n", strerror(r)); break; } } return sink->num_messages; } int stasis_message_sink_should_stay(struct stasis_message_sink *sink, int num_messages, int timeout_millis) { struct timespec deadline = make_deadline(timeout_millis); SCOPED_MUTEX(lock, &sink->lock); while (sink->num_messages == num_messages) { int r = ast_cond_timedwait(&sink->cond, &sink->lock, &deadline); if (r == ETIMEDOUT) { break; } if (r != 0) { ast_log(LOG_ERROR, "Unexpected condition error: %s\n", strerror(r)); break; } } return sink->num_messages; } int stasis_message_sink_wait_for(struct stasis_message_sink *sink, int start, stasis_wait_cb cmp_cb, const void *data, int timeout_millis) { struct timespec deadline = make_deadline(timeout_millis); SCOPED_MUTEX(lock, &sink->lock); /* wait for the start */ while (sink->num_messages < start + 1) { int r = ast_cond_timedwait(&sink->cond, &sink->lock, &deadline); if (r == ETIMEDOUT) { /* Timed out waiting for the start */ return -1; } if (r != 0) { ast_log(LOG_ERROR, "Unexpected condition error: %s\n", strerror(r)); return -2; } } while (!cmp_cb(sink->messages[start], data)) { ++start; while (sink->num_messages < start + 1) { int r = ast_cond_timedwait(&sink->cond, &sink->lock, &deadline); if (r == ETIMEDOUT) { return -1; } if (r != 0) { ast_log(LOG_ERROR, "Unexpected condition error: %s\n", strerror(r)); return -2; } } } return start; } struct stasis_message *stasis_test_message_create(void) { RAII_VAR(void *, data, NULL, ao2_cleanup); if (!stasis_test_message_type()) { return NULL; } /* We just need the unique pointer; don't care what's in it */ data = ao2_alloc(1, NULL); if (!data) { return NULL; } return stasis_message_create(stasis_test_message_type(), data); }