mirror of
https://github.com/asterisk/asterisk.git
synced 2025-08-16 11:14:52 +00:00
The fact that deleting an object from a map invalidates any iterator that happens to currently point to that object was overlooked in the initial implementation. Unfortunately, there's no way to detect that an iterator has been invalidated so the result was an occasional SEGV triggered by modules like app_chanspy that opens an iterator and can keep it open for a long period of time. The new implementation doesn't keep the underlying C++ iterator open across calls to ast_channel_iterator_next() and uses a read lock on the map to ensure that, even for the few microseconds we use the iterator, another thread can't delete a channel from under it. Even with this change, the iterators are still WAY faster than the ao2_legacy storage driver. Full details about the new implementation are located in the comments for iterator_next() in channelstorage_cpp_map_name_id.cc. Resolves: #1309
455 lines
15 KiB
C
455 lines
15 KiB
C
/*
|
|
* Asterisk -- An open source telephony toolkit.
|
|
*
|
|
* Copyright (C) 2024, Sangoma Technologies Corporation
|
|
*
|
|
* George Joseph <gjoseph@sangoma.com>
|
|
*
|
|
* See http://www.asterisk.org for more information about
|
|
* the Asterisk project. Please do not directly contact
|
|
* any of the maintainers of this project for assistance;
|
|
* the project provides a web site, mailing lists and IRC
|
|
* channels for your use.
|
|
*
|
|
* This program is free software, distributed under the terms of
|
|
* the GNU General Public License Version 2. See the LICENSE file
|
|
* at the top of the source tree.
|
|
*/
|
|
|
|
#include "asterisk.h"
|
|
#include "asterisk/options.h"
|
|
#include "channelstorage.h"
|
|
|
|
static AST_VECTOR(, const struct ast_channelstorage_driver *) storage_drivers;
|
|
|
|
int ast_channelstorage_register_driver(
|
|
const struct ast_channelstorage_driver *driver_type)
|
|
{
|
|
if (storage_drivers.elems == NULL) {
|
|
AST_VECTOR_INIT(&storage_drivers, 10);
|
|
}
|
|
return AST_VECTOR_APPEND(&storage_drivers, driver_type);
|
|
}
|
|
|
|
const struct ast_channelstorage_driver *ast_channelstorage_get_driver(
|
|
const char *driver_name)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < AST_VECTOR_SIZE(&storage_drivers); i++) {
|
|
const struct ast_channelstorage_driver *dt =
|
|
AST_VECTOR_GET(&storage_drivers, i);
|
|
if (strcasecmp(driver_name, dt->driver_name) == 0) {
|
|
return dt;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct ast_channelstorage_instance *ast_channelstorage_open(
|
|
const struct ast_channelstorage_driver *storage_driver,
|
|
const char *instance_name)
|
|
{
|
|
struct ast_channelstorage_instance *storage_instance = NULL;
|
|
|
|
storage_instance = storage_driver->open_instance(instance_name);
|
|
if (!storage_instance) {
|
|
ast_log(LOG_ERROR, "Failed to open channel storage driver '%s'\n",
|
|
storage_driver->driver_name);
|
|
return NULL;
|
|
}
|
|
|
|
return storage_instance;
|
|
};
|
|
|
|
void ast_channelstorage_close(struct ast_channelstorage_instance *storage_instance)
|
|
{
|
|
CHANNELSTORAGE_API(storage_instance, close_instance);
|
|
};
|
|
|
|
int channelstorage_exten_cb(void *obj, void *arg, void *data, int flags)
|
|
{
|
|
struct ast_channel *chan = (struct ast_channel *)obj;
|
|
const char *context = (const char *)arg;
|
|
const char *exten = (const char *)data;
|
|
int ret = 0;
|
|
|
|
ao2_lock(chan);
|
|
if (strcasecmp(ast_channel_context(chan), context) == 0 &&
|
|
strcasecmp(ast_channel_exten(chan), exten) == 0) {
|
|
ret = CMP_MATCH | ((flags & OBJ_MULTIPLE) ? 0 : CMP_STOP);
|
|
}
|
|
ao2_unlock(chan);
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct ast_channel *channelstorage_by_exten(struct ast_channelstorage_instance *driver,
|
|
const char *exten, const char *context)
|
|
{
|
|
char *l_exten = (char *) exten;
|
|
char *l_context = (char *) context;
|
|
|
|
return CHANNELSTORAGE_API(driver, callback, channelstorage_exten_cb, l_context, l_exten, 0);
|
|
}
|
|
|
|
int channelstorage_name_cb(void *obj, void *arg, void *data, int flags)
|
|
{
|
|
struct ast_channel *chan = obj;
|
|
const char *name = arg;
|
|
size_t name_len = *(size_t *) data;
|
|
int ret = 0;
|
|
|
|
if (name_len == 0) {
|
|
if(strcasecmp(ast_channel_name(chan), name) == 0) {
|
|
ret = CMP_MATCH | ((flags & OBJ_MULTIPLE) ? 0 : CMP_STOP);
|
|
}
|
|
} else {
|
|
if (strncasecmp(ast_channel_name(chan), name, name_len) == 0) {
|
|
ret = CMP_MATCH | ((flags & OBJ_MULTIPLE) ? 0 : CMP_STOP);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct ast_channel *channelstorage_by_name_or_uniqueid(struct ast_channelstorage_instance *driver,
|
|
const char *name)
|
|
{
|
|
return CHANNELSTORAGE_API(driver, get_by_name_prefix_or_uniqueid, name, 0);
|
|
}
|
|
|
|
struct ast_channel *channelstorage_by_name_prefix_or_uniqueid(struct ast_channelstorage_instance *driver,
|
|
const char *name, size_t name_len)
|
|
{
|
|
struct ast_channel *chan = NULL;
|
|
|
|
chan = CHANNELSTORAGE_API(driver, get_by_name_prefix, name, name_len);
|
|
if (chan) {
|
|
return chan;
|
|
}
|
|
|
|
if (name_len == 0) {
|
|
chan = CHANNELSTORAGE_API(driver, get_by_uniqueid, name);
|
|
}
|
|
|
|
return chan;
|
|
}
|
|
|
|
int channelstorage_uniqueid_cb(void *obj, void *arg, void *data, int flags)
|
|
{
|
|
struct ast_channel *chan = obj;
|
|
char *uniqueid = arg;
|
|
int ret = 0;
|
|
|
|
if(strcasecmp(ast_channel_uniqueid(chan), uniqueid) == 0) {
|
|
ret = CMP_MATCH | CMP_STOP;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct ast_channel *channelstorage_by_uniqueid(struct ast_channelstorage_instance *driver,
|
|
const char *uniqueid)
|
|
{
|
|
return CHANNELSTORAGE_API(driver, callback, channelstorage_uniqueid_cb, (char *)uniqueid, NULL, 0);
|
|
}
|
|
|
|
#ifdef TEST_FRAMEWORK
|
|
#include "asterisk/test.h"
|
|
#include "channel_private.h"
|
|
|
|
static void mock_channel_destructor(void *obj)
|
|
{
|
|
struct ast_channel *chan = obj;
|
|
ast_string_field_free_memory(chan);
|
|
}
|
|
|
|
struct test_info {
|
|
struct ast_test *test;
|
|
struct ast_channelstorage_instance *storage_instance;
|
|
enum ast_test_result_state res;
|
|
};
|
|
|
|
static void *test_storage_thread(void *data)
|
|
{
|
|
struct test_info *test_info = data;
|
|
struct ast_test *test = test_info->test;
|
|
struct ast_channelstorage_instance *storage_instance = test_info->storage_instance;
|
|
struct ast_channel *mock_channel;
|
|
enum ast_test_result_state res = AST_TEST_PASS;
|
|
int i;
|
|
struct timeval start;
|
|
struct timeval end;
|
|
int64_t elapsed;
|
|
char search1[128];
|
|
char search2[128];
|
|
int rc = 0;
|
|
long int rand = ast_random();
|
|
struct ast_channel_iterator *iter;
|
|
int collen = 25;
|
|
int CHANNEL_COUNT = 500;
|
|
struct ast_cli_args *cli_args = ast_test_get_cli_args(test);
|
|
struct ast_channel **test_channels;
|
|
|
|
for (i = 0; i < cli_args->argc; i++) {
|
|
if (ast_begins_with(cli_args->argv[i], "channel-count=")) {
|
|
sscanf(cli_args->argv[i], "channel-count=%d", &CHANNEL_COUNT);
|
|
}
|
|
}
|
|
test_channels = ast_calloc(CHANNEL_COUNT, sizeof(*test_channels));
|
|
ast_test_status_update(test, "%*s: %8d\n", collen, "Channel Count", CHANNEL_COUNT);
|
|
|
|
start = ast_tvnow();
|
|
for (i = 0; i < CHANNEL_COUNT; i++) {
|
|
test_channels[i] = ao2_alloc(sizeof(*mock_channel), mock_channel_destructor);
|
|
ast_test_validate_cleanup(test, test_channels[i], res, done);
|
|
ast_string_field_init(test_channels[i], 128);
|
|
ast_string_field_build(test_channels[i], name, "TestChannel-%ld-%04d-something", rand, i);
|
|
snprintf(test_channels[i]->context, AST_MAX_CONTEXT, "TestContext-%ld-%04d", rand, i % 100);
|
|
snprintf(test_channels[i]->exten, AST_MAX_EXTENSION, "TestExten-%ld-%04d", rand, i % 10);
|
|
snprintf(test_channels[i]->uniqueid.unique_id, AST_MAX_UNIQUEID, "TestUniqueid-%ld-%04d-something", rand, i);
|
|
rc = CHANNELSTORAGE_API(storage_instance, insert, test_channels[i], 0, 1);
|
|
ast_test_validate_cleanup_custom(test, rc == 0, res, done, "Unable to insert channel %s\n", test_channels[i]->name);
|
|
}
|
|
end = ast_tvnow();
|
|
elapsed = ast_tvdiff_us(end, start);
|
|
i = CHANNELSTORAGE_API(storage_instance, active_channels);
|
|
ast_test_status_update(test, "%*s: %8ld\n", collen, "create channels", elapsed);
|
|
ast_test_validate_cleanup(test, i == CHANNEL_COUNT, res, done);
|
|
|
|
start = ast_tvnow();
|
|
for (i = 0; i < CHANNEL_COUNT; i++) {
|
|
sprintf(search1, "testchannel-%ld-%04d-something", rand, i);
|
|
mock_channel = CHANNELSTORAGE_API(storage_instance, get_by_name_prefix_or_uniqueid, search1, 0);
|
|
ast_test_validate_cleanup(test, mock_channel, res, done);
|
|
ast_test_validate_cleanup(test, mock_channel == test_channels[i], res, done);
|
|
ast_test_validate_cleanup(test,
|
|
strcasecmp(ast_channel_name(mock_channel), search1) == 0, res, done);
|
|
ast_channel_unref(mock_channel);
|
|
}
|
|
end = ast_tvnow();
|
|
elapsed = ast_tvdiff_us(end, start);
|
|
ast_test_status_update(test, "%*s: %8ld\n", collen, "by name exact", elapsed);
|
|
|
|
start = ast_tvnow();
|
|
for (i = 0; i < CHANNEL_COUNT; i++) {
|
|
sprintf(search1, "TestUniqueid-%ld-%04d-something", rand, i);
|
|
mock_channel = CHANNELSTORAGE_API(storage_instance, get_by_uniqueid, search1);
|
|
ast_test_validate_cleanup(test, mock_channel, res, done);
|
|
ast_channel_unref(mock_channel);
|
|
}
|
|
end = ast_tvnow();
|
|
elapsed = ast_tvdiff_us(end, start);
|
|
ast_test_status_update(test, "%*s: %8ld\n", collen, "by uniqueid exact", elapsed);
|
|
|
|
start = ast_tvnow();
|
|
for (i = 0; i < CHANNEL_COUNT; i++) {
|
|
sprintf(search1, "TestUniqueid-%ld-%04d-something", rand, i);
|
|
mock_channel = CHANNELSTORAGE_API(storage_instance, get_by_name_prefix_or_uniqueid, search1, 0);
|
|
ast_test_validate_cleanup(test, mock_channel, res, done);
|
|
ast_channel_unref(mock_channel);
|
|
}
|
|
end = ast_tvnow();
|
|
elapsed = ast_tvdiff_us(end, start);
|
|
ast_test_status_update(test, "%*s: %8ld\n", collen, "by uniqueid via nm", elapsed);
|
|
|
|
start = ast_tvnow();
|
|
for (i = 0; i < CHANNEL_COUNT; i++) {
|
|
sprintf(search1, "TestChannel-%ld-%04d", rand, i);
|
|
mock_channel = CHANNELSTORAGE_API(storage_instance, get_by_name_prefix_or_uniqueid, search1, strlen(search1));
|
|
ast_test_validate_cleanup(test, mock_channel, res, done);
|
|
ast_channel_unref(mock_channel);
|
|
}
|
|
end = ast_tvnow();
|
|
elapsed = ast_tvdiff_us(end, start);
|
|
ast_test_status_update(test, "%*s: %8ld\n", collen, "by name prefix", elapsed);
|
|
|
|
start = ast_tvnow();
|
|
for (i = 0; i < CHANNEL_COUNT; i++) {
|
|
sprintf(search1, "TestContext-%ld-%04d", rand, i % 100);
|
|
sprintf(search2, "TestExten-%ld-%04d", rand, i % 10);
|
|
mock_channel = CHANNELSTORAGE_API(storage_instance, get_by_exten, search2, search1);
|
|
ast_test_validate_cleanup(test, mock_channel, res, done);
|
|
ast_channel_unref(mock_channel);
|
|
}
|
|
end = ast_tvnow();
|
|
elapsed = ast_tvdiff_us(end, start);
|
|
ast_test_status_update(test, "%*s: %8ld\n", collen, "by context/exten", elapsed);
|
|
|
|
i = 0;
|
|
start = ast_tvnow();
|
|
iter = CHANNELSTORAGE_API(storage_instance, iterator_all_new);
|
|
for (; (mock_channel = CHANNELSTORAGE_API(storage_instance, iterator_next, iter));
|
|
ast_channel_unref(mock_channel)) {
|
|
i++;
|
|
}
|
|
CHANNELSTORAGE_API(storage_instance, iterator_destroy, iter);
|
|
end = ast_tvnow();
|
|
elapsed = ast_tvdiff_us(end, start);
|
|
ast_test_status_update(test, "%*s: %8ld\n", collen, "iter all chan", elapsed);
|
|
ast_test_validate_cleanup_custom(test, i == CHANNEL_COUNT, res, done,
|
|
"Expected %d channels, got %d, in container: %d\n", CHANNEL_COUNT, i,
|
|
CHANNELSTORAGE_API(storage_instance, active_channels));
|
|
|
|
i = 0;
|
|
start = ast_tvnow();
|
|
sprintf(search1, "TestChannel-%ld-%03d", rand, (CHANNEL_COUNT - 11) / 10);
|
|
iter = CHANNELSTORAGE_API(storage_instance, iterator_by_name_new, search1, strlen(search1));
|
|
ast_test_validate_cleanup(test, iter != NULL, res, done);
|
|
for (; (mock_channel = CHANNELSTORAGE_API(storage_instance, iterator_next, iter));
|
|
ast_channel_unref(mock_channel)) {
|
|
ast_test_validate_cleanup_custom(test, strncmp(search1,
|
|
ast_channel_name(mock_channel), strlen(search1)) == 0, res, done, "Expected %s got %s\n",
|
|
search1, ast_channel_name(mock_channel));
|
|
i++;
|
|
}
|
|
CHANNELSTORAGE_API(storage_instance, iterator_destroy, iter);
|
|
end = ast_tvnow();
|
|
elapsed = ast_tvdiff_us(end, start);
|
|
ast_test_status_update(test, "%*s: %8ld\n", collen, "iter 10 partial name", elapsed);
|
|
ast_test_validate_cleanup_custom(test, i == 10, res, done,
|
|
"Expected %d channels, got %d, in container: %d\n", 10, i,
|
|
CHANNELSTORAGE_API(storage_instance, active_channels));
|
|
|
|
i = 0;
|
|
start = ast_tvnow();
|
|
sprintf(search1, "TestContext-%ld-%04d", rand, 50);
|
|
sprintf(search2, "TestExten-%ld-%04d", rand, 0);
|
|
iter = CHANNELSTORAGE_API(storage_instance, iterator_by_exten_new, search2, search1);
|
|
ast_test_validate_cleanup(test, iter != NULL, res, done);
|
|
for (; (mock_channel = CHANNELSTORAGE_API(storage_instance, iterator_next, iter));
|
|
ast_channel_unref(mock_channel)) {
|
|
ast_test_validate_cleanup_custom(test,
|
|
(strcmp(search1, mock_channel->context) == 0 &&
|
|
strcmp(search2, mock_channel->exten) == 0), res, done, "Expected %s-%s got %s-%s\n",
|
|
search1, search2, mock_channel->context, mock_channel->exten);
|
|
i++;
|
|
}
|
|
CHANNELSTORAGE_API(storage_instance, iterator_destroy, iter);
|
|
end = ast_tvnow();
|
|
elapsed = ast_tvdiff_us(end, start);
|
|
ast_test_status_update(test, "%*s: %8ld\n", collen, "iter context/exten", elapsed);
|
|
ast_test_validate_cleanup_custom(test, i == (CHANNEL_COUNT / 100), res, done,
|
|
"Expected %d channels, got %d, in container: %d\n", (CHANNEL_COUNT / 100), i,
|
|
CHANNEL_COUNT);
|
|
|
|
done:
|
|
CHANNELSTORAGE_API(storage_instance, unlock);
|
|
|
|
start = ast_tvnow();
|
|
for (i = 0; i < CHANNEL_COUNT; i++) {
|
|
if (test_channels[i]) {
|
|
rc = CHANNELSTORAGE_API(storage_instance, remove, test_channels[i], 0);
|
|
ast_channel_unref(test_channels[i]);
|
|
test_channels[i] = NULL;
|
|
}
|
|
}
|
|
end = ast_tvnow();
|
|
elapsed = ast_tvdiff_us(end, start);
|
|
ast_test_status_update(test, "%*s: %8ld\n", collen, "del all channels", elapsed);
|
|
ast_test_validate_cleanup(test, i == CHANNEL_COUNT, res, done);
|
|
rc = CHANNELSTORAGE_API(storage_instance, active_channels);
|
|
ast_test_validate_cleanup_custom(test, rc == 0, res, final,
|
|
"There are still %d channels in the container\n", rc);
|
|
|
|
test_info->res = res;
|
|
return NULL;
|
|
|
|
final:
|
|
iter = CHANNELSTORAGE_API(storage_instance, iterator_all_new);
|
|
for (; (mock_channel = CHANNELSTORAGE_API(storage_instance, iterator_next, iter));
|
|
ast_channel_unref(mock_channel)) {
|
|
ast_test_status_update(test, "%p %s\n", mock_channel, ast_channel_name(mock_channel));
|
|
i++;
|
|
}
|
|
CHANNELSTORAGE_API(storage_instance, iterator_destroy, iter);
|
|
|
|
test_info->res = res;
|
|
return NULL;
|
|
}
|
|
|
|
static enum ast_test_result_state test_storage(struct ast_test_info *info,
|
|
enum ast_test_command cmd, struct ast_test *test,
|
|
const char *storage_name, const char *summary)
|
|
{
|
|
const struct ast_channelstorage_driver *storage_driver;
|
|
struct test_info ti = {
|
|
.test = test,
|
|
.storage_instance = NULL,
|
|
.res = AST_TEST_PASS,
|
|
};
|
|
pthread_t thread;
|
|
int rc = 0;
|
|
|
|
switch (cmd) {
|
|
case TEST_INIT:
|
|
info->name = storage_name;
|
|
info->category = "/main/channelstorage/";
|
|
info->summary = summary;
|
|
info->description = info->summary;
|
|
return AST_TEST_NOT_RUN;
|
|
case TEST_EXECUTE:
|
|
break;
|
|
}
|
|
|
|
storage_driver = ast_channelstorage_get_driver(info->name);
|
|
if (!storage_driver) {
|
|
ast_test_status_update(test, "Storage driver %s not registered\n", info->name);
|
|
return AST_TEST_NOT_RUN;
|
|
}
|
|
ti.storage_instance = ast_channelstorage_open(storage_driver, "channels_test");
|
|
ast_test_validate(test, ti.storage_instance, res);
|
|
|
|
rc = ast_pthread_create(&thread, NULL, test_storage_thread, &ti);
|
|
if (rc) {
|
|
ast_channelstorage_close(ti.storage_instance);
|
|
ast_test_status_update(test, "Failed to create thread: %s\n", strerror(rc));
|
|
return AST_TEST_FAIL;
|
|
}
|
|
pthread_join(thread, NULL);
|
|
ast_channelstorage_close(ti.storage_instance);
|
|
|
|
return ti.res;
|
|
}
|
|
|
|
#define DEFINE_STORAGE_TEST(_name) \
|
|
AST_TEST_DEFINE(_name) \
|
|
{ \
|
|
return test_storage(info, cmd, test, #_name, "Channel Storage test for " #_name); \
|
|
}
|
|
|
|
DEFINE_STORAGE_TEST(ao2_legacy)
|
|
|
|
DEFINE_STORAGE_TEST(cpp_map_name_id)
|
|
|
|
#define REGISTER_STORAGE_TEST(_name) \
|
|
({ \
|
|
if (ast_channelstorage_get_driver(#_name)) { \
|
|
AST_TEST_REGISTER(_name); \
|
|
} \
|
|
})
|
|
#endif
|
|
|
|
static void channelstorage_shutdown(void)
|
|
{
|
|
#ifdef TEST_FRAMEWORK
|
|
/* Unregistering a test that wasn't previously registered is safe */
|
|
AST_TEST_UNREGISTER(cpp_map_name_id);
|
|
AST_TEST_UNREGISTER(ao2_legacy);
|
|
#endif
|
|
}
|
|
|
|
int ast_channelstorage_init(void)
|
|
{
|
|
#ifdef TEST_FRAMEWORK
|
|
/* Tests run in the reverse order registered */
|
|
REGISTER_STORAGE_TEST(cpp_map_name_id);
|
|
AST_TEST_REGISTER(ao2_legacy);
|
|
#endif
|
|
ast_register_cleanup(channelstorage_shutdown);
|
|
|
|
return 0;
|
|
}
|
|
|