diff --git a/src/at_queue.c b/src/at_queue.c index 69c39109..1ca81787 100644 --- a/src/at_queue.c +++ b/src/at_queue.c @@ -316,7 +316,7 @@ void at_queue_handle_result(struct pvt* pvt, at_res_t res) { at_queue_remove_cmd void at_queue_flush(struct pvt* pvt) { - while (AST_LIST_FIRST(&pvt->at_queue)) { + while (!AST_LIST_EMPTY(&pvt->at_queue)) { at_queue_remove(pvt); } } diff --git a/src/chan_quectel.c b/src/chan_quectel.c index 195ec390..10213a1c 100644 --- a/src/chan_quectel.c +++ b/src/chan_quectel.c @@ -67,6 +67,8 @@ #include "smsdb.h" #include "tty.h" +static const int NUM_PVT_BUCKETS = 7; + static int soundcard_init(struct pvt* pvt) { const struct ast_format* const fmt = pvt_get_audio_format(pvt); @@ -106,10 +108,18 @@ void pvt_disconnect(struct pvt* pvt) if (!PVT_NO_CHANS(pvt)) { struct cpvt* cpvt; AST_LIST_TRAVERSE(&(pvt->chans), cpvt, entry) { + PVT_STATE(pvt, chan_count[cpvt->state])--; + PVT_STATE(pvt, chansno)--; + at_hangup_immediately(cpvt, AST_CAUSE_NORMAL_UNSPECIFIED); + CPVT_SET_FLAG(cpvt, CALL_FLAG_DISCONNECTING); CPVT_RESET_FLAG(cpvt, CALL_FLAG_NEED_HANGUP); cpvt_change_state(cpvt, CALL_STATE_RELEASED, AST_CAUSE_NORMAL_UNSPECIFIED); } + + while (!AST_LIST_EMPTY(&(pvt->chans))) { + AST_LIST_REMOVE_HEAD(&(pvt->chans), entry); + } } if (pvt->initialized) { @@ -200,7 +210,7 @@ void pvt_disconnect(struct pvt* pvt) memset(&pvt->stat, 0, sizeof(pvt->stat)); if (pvt->local_format_cap) { - ao2_cleanup(pvt->local_format_cap); + ao2_ref(pvt->local_format_cap, -1); pvt->local_format_cap = NULL; } @@ -284,27 +294,106 @@ static void pvt_start(struct pvt* const pvt) tty_close(CONF_UNIQ(pvt, data_tty), pvt->data_fd); } -#/* */ +static void pvt_finish(struct pvt* const pvt) +{ + pvt_monitor_stop(pvt); + at_queue_flush(pvt); +} -static void pvt_free(struct pvt* const pvt) +static int pvt_finish_cb(void* obj, attribute_unused void* arg, attribute_unused int flags) { + SCOPED_AO2LOCK(pvt_lock, obj); + struct pvt* const pvt = obj; + pvt_monitor_stop(pvt); at_queue_flush(pvt); + return 0; +} + +static void pvt_destroy(void* obj) +{ + SCOPED_AO2LOCK(pvt_lock, obj); + struct pvt* const pvt = (struct pvt* const)obj; ast_string_field_free_memory(pvt); - ast_mutex_unlock(&pvt->lock); - ast_mutex_destroy(&pvt->lock); - ast_free(pvt); } -#/* */ +// device manager -static void pvt_destroy(struct pvt* const pvt) +static void dev_manager_process_pvt(struct pvt* const pvt) { - ast_mutex_lock(&pvt->lock); - pvt_monitor_stop(pvt); - pvt_free(pvt); + if (pvt->must_remove) { + return; + } + + if (pvt->restart_time != RESTATE_TIME_NOW) { + return; + } + if (pvt->desired_state == pvt->current_state) { + return; + } + + switch (pvt->desired_state) { + case DEV_STATE_RESTARTED: + ast_debug(4, "[dev-manager][%s] Restarting device\n", PVT_ID(pvt)); + pvt_monitor_stop(pvt); + pvt->desired_state = DEV_STATE_STARTED; + /* fall through */ + + case DEV_STATE_STARTED: + ast_debug(4, "[dev-manager][%s] Starting device\n", PVT_ID(pvt)); + pvt_start(pvt); + break; + + case DEV_STATE_REMOVED: + ast_debug(4, "[dev-manager][%s] Removing device\n", PVT_ID(pvt)); + pvt_monitor_stop(pvt); + pvt->must_remove = 1; + break; + + case DEV_STATE_STOPPED: + ast_debug(4, "[dev-manager][%s] Stopping device\n", PVT_ID(pvt)); + pvt_monitor_stop(pvt); + break; + } } -// device manager +static void dev_manager_process_pvts(struct public_state* const state) +{ + struct pvt* pvt; + struct ao2_iterator i = ao2_iterator_init(state->pvts, 0); + while ((pvt = ao2_iterator_next(&i))) { + if (ao2_lock(pvt)) { + ao2_ref(pvt, -1); + continue; + } + dev_manager_process_pvt(pvt); + AO2_UNLOCK_AND_UNREF(pvt); + } + ao2_iterator_destroy(&i); +} + +static void dev_manager_remove_pvts(struct public_state* const state) +{ + struct pvt* pvt; + struct ao2_iterator i = ao2_iterator_init(state->pvts, 0); + while ((pvt = ao2_iterator_next(&i))) { + if (ao2_trylock(pvt)) { + ao2_ref(pvt, -1); + continue; + } + + if (pvt->must_remove) { + ast_debug(4, "[dev-manager][%s] Freeing device\n", PVT_ID(pvt)); + pvt_finish(pvt); + ao2_unlock(pvt); + ao2_unlink(state->pvts, pvt); + } else { + ao2_unlock(pvt); + } + + ao2_ref(pvt, -1); + } + ao2_iterator_destroy(&i); +} static const eventfd_t DEV_MANAGER_CMD_SCAN = 1; static const eventfd_t DEV_MANAGER_CMD_STOP = 2; @@ -341,71 +430,13 @@ static void dev_manager_threadproc_state(struct public_state* const state) } // timeout - struct pvt* pvt; - /* read lock for avoid deadlock when IMEI/IMSI discovery */ - AST_RWLIST_RDLOCK(&state->devices); - AST_RWLIST_TRAVERSE(&state->devices, pvt, entry) { - SCOPED_MUTEX(pvt_lock, &pvt->lock); - - if (pvt->must_remove) { - continue; - } - - if (pvt->restart_time != RESTATE_TIME_NOW) { - continue; - } - if (pvt->desired_state == pvt->current_state) { - continue; - } - - switch (pvt->desired_state) { - case DEV_STATE_RESTARTED: - ast_debug(4, "[dev-manager][%s] Restarting device\n", PVT_ID(pvt)); - pvt_monitor_stop(pvt); - pvt->desired_state = DEV_STATE_STARTED; - /* fall through */ - - case DEV_STATE_STARTED: - ast_debug(4, "[dev-manager][%s] Starting device\n", PVT_ID(pvt)); - pvt_start(pvt); - break; - - case DEV_STATE_REMOVED: - ast_debug(4, "[dev-manager][%s] Removing device\n", PVT_ID(pvt)); - pvt_monitor_stop(pvt); - pvt->must_remove = 1; - break; + dev_manager_process_pvts(state); - case DEV_STATE_STOPPED: - ast_debug(4, "[dev-manager][%s] Stopping device\n", PVT_ID(pvt)); - pvt_monitor_stop(pvt); - break; - } - } - AST_RWLIST_UNLOCK(&state->devices); - - /* actual device removal here for avoid long (discovery) time write lock on device list in loop above */ - - if (AST_RWLIST_TRYWRLOCK(&state->devices)) { + if (ao2_trylock(state->pvts)) { continue; } - - AST_RWLIST_TRAVERSE_SAFE_BEGIN(&state->devices, pvt, entry) - { - if (ast_mutex_trylock(&pvt->lock)) { - continue; - } - - if (pvt->must_remove) { - ast_debug(4, "[dev-manager][%s] Freeing device\n", PVT_ID(pvt)); - AST_RWLIST_REMOVE_CURRENT(entry); - pvt_free(pvt); - } else { - ast_mutex_unlock(&pvt->lock); - } - } - AST_RWLIST_TRAVERSE_SAFE_END; - AST_RWLIST_UNLOCK(&state->devices); + dev_manager_remove_pvts(state); + ao2_unlock(state->pvts); } } @@ -610,7 +641,7 @@ void pvt_unlock(struct pvt* const pvt) return; } - ast_mutex_unlock(&pvt->lock); + AO2_UNLOCK_AND_UNREF(pvt); } int pvt_taskproc_trylock_and_execute(struct pvt* pvt, void (*task_exe)(struct pvt* pvt), const char* task_name) @@ -619,21 +650,21 @@ int pvt_taskproc_trylock_and_execute(struct pvt* pvt, void (*task_exe)(struct pv return 0; } - if (ast_mutex_trylock(&pvt->lock)) { + if (ao2_trylock(pvt)) { ast_debug(4, "[%s] Task skipping: no lock\n", S_OR(task_name, "UNKNOWN")); return 0; } if (pvt->terminate_monitor) { ast_debug(5, "[%s][%s] Task skipping: monitor thread terminated\n", PVT_ID(pvt), S_OR(task_name, "UNKNOWN")); - ast_mutex_unlock(&pvt->lock); + ao2_unlock(pvt); return 0; } ast_debug(5, "[%s][%s] Task executing\n", PVT_ID(pvt), S_OR(task_name, "UNKNOWN")); task_exe(pvt); ast_debug(6, "[%s][%s] Task executed\n", PVT_ID(pvt), S_OR(task_name, "UNKNOWN")); - ast_mutex_unlock(&pvt->lock); + ao2_unlock(pvt); return 0; } @@ -643,7 +674,7 @@ int pvt_taskproc_lock_and_execute(struct pvt_taskproc_data* ptd, void (*task_exe return 0; } - SCOPED_MUTEX(plock, &ptd->pvt->lock); + SCOPED_AO2LOCK(plock, ptd->pvt); if (ptd->pvt->terminate_monitor) { ast_debug(5, "[%s][%s] Task skipping: monitor thread terminated\n", PVT_ID(ptd->pvt), S_OR(task_name, "UNKNOWN")); @@ -661,18 +692,21 @@ int pvt_taskproc_lock_and_execute(struct pvt_taskproc_data* ptd, void (*task_exe struct pvt* pvt_find_ex(struct public_state* state, const char* name) { struct pvt* pvt; + struct ao2_iterator i = ao2_iterator_init(state->pvts, 0); + while ((pvt = ao2_iterator_next(&i))) { + if (ao2_lock(pvt)) { + ao2_ref(pvt, -1); + continue; + } - AST_RWLIST_RDLOCK(&state->devices); - AST_RWLIST_TRAVERSE(&state->devices, pvt, entry) { - ast_mutex_lock(&pvt->lock); if (!strcmp(PVT_ID(pvt), name)) { - break; + ao2_iterator_destroy(&i); + return pvt; } - ast_mutex_unlock(&pvt->lock); + AO2_UNLOCK_AND_UNREF(pvt); } - AST_RWLIST_UNLOCK(&state->devices); - - return pvt; + ao2_iterator_destroy(&i); + return NULL; } #/* return locked pvt or NULL */ @@ -683,7 +717,7 @@ struct pvt* pvt_find_by_ext(const char* name) if (pvt) { if (!pvt_enabled(pvt)) { - ast_mutex_unlock(&pvt->lock); + AO2_UNLOCK_AND_UNREF(pvt); chan_quectel_err = E_DEVICE_DISABLED; pvt = NULL; } @@ -720,15 +754,17 @@ static struct pvt* pvt_find_by_resource_fn(struct public_state* state, const cha *exists = 0; /* Find requested device and make sure it's connected and initialized. */ - AST_RWLIST_RDLOCK(&state->devices); if (((resource[0] == 'g') || (resource[0] == 'G')) && ((resource[1] >= '0') && (resource[1] <= '9'))) { errno = 0; group = (int)strtol(&resource[1], (char**)NULL, 10); if (errno != EINVAL) { - AST_RWLIST_TRAVERSE(&state->devices, pvt, entry) { - ast_mutex_lock(&pvt->lock); - + struct ao2_iterator i = ao2_iterator_init(state->pvts, 0); + while ((pvt = ao2_iterator_next(&i))) { + if (ao2_lock(pvt)) { + ao2_ref(pvt, -1); + continue; + } if (CONF_SHARED(pvt, group) == group) { *exists = 1; if (test_fn(pvt)) { @@ -736,19 +772,24 @@ static struct pvt* pvt_find_by_resource_fn(struct public_state* state, const cha break; } } - ast_mutex_unlock(&pvt->lock); + AO2_UNLOCK_AND_UNREF(pvt); } + ao2_iterator_destroy(&i); } } else if (((resource[0] == 'r') || (resource[0] == 'R')) && ((resource[1] >= '0') && (resource[1] <= '9'))) { errno = 0; group = (int)strtol(&resource[1], (char**)NULL, 10); if (errno != EINVAL) { /* Generate a list of all available devices */ - j = ARRAY_LEN(round_robin); - c = 0; - last_used = 0; - AST_RWLIST_TRAVERSE(&state->devices, pvt, entry) { - SCOPED_MUTEX(pvt_lock, &pvt->lock); + j = ARRAY_LEN(round_robin); + c = 0; + last_used = 0; + struct ao2_iterator it = ao2_iterator_init(state->pvts, 0); + while ((pvt = ao2_iterator_next(&it))) { + if (ao2_lock(pvt)) { + ao2_ref(pvt, -1); + continue; + } if (CONF_SHARED(pvt, group) == group) { round_robin[c] = pvt; if (pvt->group_last_used == 1) { @@ -756,13 +797,15 @@ static struct pvt* pvt_find_by_resource_fn(struct public_state* state, const cha last_used = c; } - ++c; - - if (c == j) { + ao2_unlock(pvt); + if (++c == j) { break; } + } else { + AO2_UNLOCK_AND_UNREF(pvt); } } + ao2_iterator_destroy(&it); /* Search for a available device starting at the last used device */ for (i = 0, j = last_used + 1; i < c; i++, j++) { @@ -770,25 +813,36 @@ static struct pvt* pvt_find_by_resource_fn(struct public_state* state, const cha j = 0; } - pvt = round_robin[j]; - *exists = 1; + pvt = round_robin[j]; + if (found) { + ao2_ref(pvt, -1); + continue; + } - ast_mutex_lock(&pvt->lock); + if (ao2_lock(pvt)) { + ao2_ref(pvt, -1); + continue; + } + *exists = 1; if (test_fn(pvt)) { pvt->group_last_used = 1; found = pvt; - break; + } else { + AO2_UNLOCK_AND_UNREF(pvt); } - ast_mutex_unlock(&pvt->lock); } } } else if (((resource[0] == 'p') || (resource[0] == 'P')) && resource[1] == ':') { /* Generate a list of all available devices */ - j = ARRAY_LEN(round_robin); - c = 0; - last_used = 0; - AST_RWLIST_TRAVERSE(&state->devices, pvt, entry) { - SCOPED_MUTEX(pvt_lock, &pvt->lock); + j = ARRAY_LEN(round_robin); + c = 0; + last_used = 0; + struct ao2_iterator it = ao2_iterator_init(state->pvts, 0); + while ((pvt = ao2_iterator_next(&it))) { + if (ao2_lock(pvt)) { + ao2_ref(pvt, -1); + continue; + } if (!strcmp(pvt->provider_name, &resource[2])) { round_robin[c] = pvt; if (pvt->prov_last_used == 1) { @@ -796,13 +850,15 @@ static struct pvt* pvt_find_by_resource_fn(struct public_state* state, const cha last_used = c; } - ++c; - - if (c == j) { + ao2_unlock(pvt); + if (++c == j) { break; } + } else { + AO2_UNLOCK_AND_UNREF(pvt); } } + ao2_iterator_destroy(&it); /* Search for a available device starting at the last used device */ for (i = 0, j = last_used + 1; i < c; ++i, ++j) { @@ -810,40 +866,53 @@ static struct pvt* pvt_find_by_resource_fn(struct public_state* state, const cha j = 0; } - pvt = round_robin[j]; - *exists = 1; + pvt = round_robin[j]; + if (found) { + ao2_ref(pvt, -1); + continue; + } - ast_mutex_lock(&pvt->lock); + if (ao2_lock(pvt)) { + ao2_ref(pvt, -1); + continue; + } + + *exists = 1; if (test_fn(pvt)) { pvt->prov_last_used = 1; found = pvt; - break; + } else { + AO2_UNLOCK_AND_UNREF(pvt); } - ast_mutex_unlock(&pvt->lock); } } else if (((resource[0] == 's') || (resource[0] == 'S')) && resource[1] == ':') { /* Generate a list of all available devices */ - j = ARRAY_LEN(round_robin); - c = 0; - last_used = 0; - i = strlen(&resource[2]); - - AST_RWLIST_TRAVERSE(&state->devices, pvt, entry) { - SCOPED_MUTEX(pvt_lock, &pvt->lock); - if (!strncmp(pvt->imsi, &resource[2], i)) { + j = ARRAY_LEN(round_robin); + c = 0; + last_used = 0; + const size_t len = strlen(&resource[2]); + struct ao2_iterator it = ao2_iterator_init(state->pvts, 0); + while ((pvt = ao2_iterator_next(&it))) { + if (ao2_lock(pvt)) { + ao2_ref(pvt, -1); + continue; + } + if (!strncmp(pvt->imsi, &resource[2], len)) { round_robin[c] = pvt; if (pvt->sim_last_used == 1) { pvt->sim_last_used = 0; last_used = c; } - ++c; - - if (c == j) { + ao2_unlock(pvt); + if (++c == j) { break; } + } else { + AO2_UNLOCK_AND_UNREF(pvt); } } + ao2_iterator_destroy(&it); /* Search for a available device starting at the last used device */ for (i = 0, j = last_used + 1; i < c; ++i, ++j) { @@ -851,56 +920,81 @@ static struct pvt* pvt_find_by_resource_fn(struct public_state* state, const cha j = 0; } - pvt = round_robin[j]; - *exists = 1; + pvt = round_robin[j]; + if (found) { + ao2_ref(pvt, -1); + continue; + } + + if (ao2_lock(pvt)) { + ao2_ref(pvt, -1); + continue; + } - ast_mutex_lock(&pvt->lock); + *exists = 1; if (test_fn(pvt)) { pvt->sim_last_used = 1; found = pvt; - break; + } else { + AO2_UNLOCK_AND_UNREF(pvt); } - ast_mutex_unlock(&pvt->lock); } } else if (((resource[0] == 'i') || (resource[0] == 'I')) && resource[1] == ':') { - AST_RWLIST_TRAVERSE(&state->devices, pvt, entry) { - ast_mutex_lock(&pvt->lock); + struct ao2_iterator it = ao2_iterator_init(state->pvts, 0); + while ((pvt = ao2_iterator_next(&it))) { + if (ao2_lock(pvt)) { + ao2_ref(pvt, -1); + continue; + } if (!strcmp(pvt->imei, &resource[2])) { *exists = 1; if (test_fn(pvt)) { found = pvt; break; } + } else { + AO2_UNLOCK_AND_UNREF(pvt); } - ast_mutex_unlock(&pvt->lock); } + ao2_iterator_destroy(&it); } else if (((resource[0] == 'j') || (resource[0] == 'J')) && resource[1] == ':') { - AST_RWLIST_TRAVERSE(&state->devices, pvt, entry) { - ast_mutex_lock(&pvt->lock); + struct ao2_iterator it = ao2_iterator_init(state->pvts, 0); + while ((pvt = ao2_iterator_next(&it))) { + if (ao2_lock(pvt)) { + ao2_ref(pvt, -1); + continue; + } if (!strcmp(pvt->iccid, &resource[2])) { *exists = 1; if (test_fn(pvt)) { found = pvt; break; } + } else { + AO2_UNLOCK_AND_UNREF(pvt); } - ast_mutex_unlock(&pvt->lock); } + ao2_iterator_destroy(&it); } else { - AST_RWLIST_TRAVERSE(&state->devices, pvt, entry) { - ast_mutex_lock(&pvt->lock); + struct ao2_iterator it = ao2_iterator_init(state->pvts, 0); + while ((pvt = ao2_iterator_next(&it))) { + if (ao2_lock(pvt)) { + ao2_ref(pvt, -1); + continue; + } if (!strcmp(PVT_ID(pvt), resource)) { *exists = 1; if (test_fn(pvt)) { found = pvt; break; } + } else { + AO2_UNLOCK_AND_UNREF(pvt); } - ast_mutex_unlock(&pvt->lock); } + ao2_iterator_destroy(&it); } - AST_RWLIST_UNLOCK(&state->devices); return found; } @@ -1112,15 +1206,13 @@ void pvt_get_status(const struct pvt* const pvt, struct ast_json* status) static struct pvt* pvt_create(const pvt_config_t* settings) { - struct pvt* const pvt = ast_calloc(1, sizeof(*pvt) + 1u); + struct pvt* const pvt = ao2_alloc(sizeof(struct pvt) + 1u, pvt_destroy); if (!pvt) { ast_log(LOG_ERROR, "[%s] Skipping device: Error allocating memory\n", UCONFIG(settings, id)); return NULL; } - ast_mutex_init(&pvt->lock); - AST_LIST_HEAD_INIT_NOLOCK(&pvt->at_queue); AST_LIST_HEAD_INIT_NOLOCK(&pvt->chans); @@ -1135,7 +1227,7 @@ static struct pvt* pvt_create(const pvt_config_t* settings) pvt->incoming_sms_type = RES_UNKNOWN; pvt->desired_state = SCONFIG(settings, init_state); - ast_string_field_init(pvt, 15); + ast_string_field_init(pvt, 14); ast_string_field_set(pvt, provider_name, "NONE"); ast_string_field_set(pvt, subscriber_number, NULL); @@ -1216,29 +1308,30 @@ int pvt_set_act(struct pvt* pvt, int act) #/* */ -static void mark_must_remove(public_state_t* const state) +static int pvt_mark_must_remove_cb(void* obj, attribute_unused void* arg, attribute_unused int flags) { - struct pvt* pvt; - - /* FIXME: deadlock avoid ? */ - AST_RWLIST_RDLOCK(&state->devices); - AST_RWLIST_TRAVERSE(&state->devices, pvt, entry) { - SCOPED_MUTEX(pvt_lock, &pvt->lock); - pvt->must_remove = 1; - } - AST_RWLIST_UNLOCK(&state->devices); + SCOPED_AO2LOCK(pvt_lock, obj); + struct pvt* pvt = obj; + pvt->must_remove = 1; + return 0; } +static void mark_must_remove(public_state_t* const state) { ao2_callback(state->pvts, OBJ_NODATA, pvt_mark_must_remove_cb, NULL); } + static void mark_remove(public_state_t* const state, const restate_time_t when, unsigned int* reload_cnt) { struct pvt* pvt; /* FIXME: deadlock avoid ? */ /* schedule removal of devices not listed in config file or disabled */ - AST_RWLIST_RDLOCK(&state->devices); - AST_RWLIST_TRAVERSE(&state->devices, pvt, entry) { - SCOPED_MUTEX(pvt_lock, &pvt->lock); + struct ao2_iterator i = ao2_iterator_init(state->pvts, 0); + while ((pvt = ao2_iterator_next(&i))) { + if (ao2_lock(pvt)) { + ao2_ref(pvt, -1); + continue; + } if (!pvt->must_remove) { + AO2_UNLOCK_AND_UNREF(pvt); continue; } @@ -1249,8 +1342,9 @@ static void mark_remove(public_state_t* const state, const restate_time_t when, } else { pvt->restart_time = when; } + AO2_UNLOCK_AND_UNREF(pvt); } - AST_RWLIST_UNLOCK(&state->devices); + ao2_iterator_destroy(&i); } static int reload_config(public_state_t* state, int recofigure, restate_time_t when, unsigned* reload_immediality) @@ -1305,13 +1399,14 @@ static int reload_config(public_state_t* state, int recofigure, restate_time_t w if (!new_pvt) { continue; } - /* FIXME: deadlock avoid ? */ - AST_RWLIST_WRLOCK(&state->devices); - AST_RWLIST_INSERT_TAIL(&state->devices, new_pvt, entry); - AST_RWLIST_UNLOCK(&state->devices); - reload_now++; - ast_log(LOG_NOTICE, "[%s] Loaded device\n", PVT_ID(new_pvt)); + if (ao2_link(state->pvts, new_pvt)) { + reload_now++; + ast_log(LOG_NOTICE, "[%s] Device loaded\n", PVT_ID(new_pvt)); + } else { + ast_log(LOG_ERROR, "[%s] Could not add device to container\n", PVT_ID(new_pvt)); + } + ao2_ref(new_pvt, -1); } } @@ -1328,14 +1423,14 @@ static int reload_config(public_state_t* state, int recofigure, restate_time_t w static void devices_destroy(public_state_t* state) { - struct pvt* pvt; + ao2_ref(state->pvts, -1); + state->pvts = NULL; +} - /* Destroy the device list */ - AST_RWLIST_WRLOCK(&state->devices); - while ((pvt = AST_RWLIST_REMOVE_HEAD(&state->devices, entry))) { - pvt_destroy(pvt); - } - AST_RWLIST_UNLOCK(&state->devices); +static void devices_finish(public_state_t* state) +{ + ao2_callback(state->pvts, OBJ_NODATA, pvt_finish_cb, NULL); + devices_destroy(state); } const struct ast_format* pvt_get_audio_format(const struct pvt* const pvt) @@ -1448,6 +1543,20 @@ static unsigned int get_default_framing() { return PTIME_CAPTURE } #endif +static int pvt_hash_cb(const void* obj, const int flags) +{ + const struct pvt* const pvt = obj; + + return ast_str_case_hash(PVT_ID(pvt)); +} + +static int pvt_cmp_cb(void* obj, void* arg, int flags) +{ + const struct pvt *const pvt = obj, *const pvt2 = arg; + + return !strcasecmp(PVT_ID(pvt), PVT_ID(pvt2)) ? CMP_MATCH | CMP_STOP : 0; +} + static int public_state_init(struct public_state* state) { int rv = AST_MODULE_LOAD_DECLINE; @@ -1460,11 +1569,18 @@ static int public_state_init(struct public_state* state) state->dev_manager_event = eventfd_create(); state->dev_manager_thread = AST_PTHREADT_NULL; - AST_RWLIST_HEAD_INIT(&state->devices); + state->pvts = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NUM_PVT_BUCKETS, pvt_hash_cb, NULL, pvt_cmp_cb); + if (!state->pvts) { + ast_threadpool_shutdown(state->threadpool); + return rv; + } + + ao2_callback(state->pvts, OBJ_NODATA, pvt_mark_must_remove_cb, NULL); if (reload_config(state, 0, RESTATE_TIME_NOW, NULL)) { ast_log(LOG_ERROR, "Errors reading config file " CONFIG_FILE ", Not loading module\n"); - AST_RWLIST_HEAD_DESTROY(&state->devices); + devices_destroy(state); + ast_threadpool_shutdown(state->threadpool); return rv; } @@ -1473,7 +1589,7 @@ static int public_state_init(struct public_state* state) if (dev_manager_start(state)) { ast_log(LOG_ERROR, "Unable to create device manager thread\n"); devices_destroy(state); - AST_RWLIST_HEAD_DESTROY(&state->devices); + ast_threadpool_shutdown(state->threadpool); return rv; } @@ -1483,7 +1599,7 @@ static int public_state_init(struct public_state* state) ast_log(LOG_ERROR, "Unable to create channel capabilities\n"); dev_manager_stop(state); devices_destroy(state); - AST_RWLIST_HEAD_DESTROY(&state->devices); + ast_threadpool_shutdown(state->threadpool); return rv; } @@ -1494,11 +1610,11 @@ static int public_state_init(struct public_state* state) if (ast_channel_register(&channel_tech)) { ast_log(LOG_ERROR, "Unable to register channel class %s\n", channel_tech.type); - ao2_cleanup(channel_tech.capabilities); + ao2_ref(channel_tech.capabilities, -1); channel_tech.capabilities = NULL; dev_manager_stop(state); devices_destroy(state); - AST_RWLIST_HEAD_DESTROY(&state->devices); + ast_threadpool_shutdown(state->threadpool); return rv; } @@ -1521,7 +1637,7 @@ static void public_state_fini(struct public_state* const state) { /* First, take us out of the channel loop */ ast_channel_unregister(&channel_tech); - ao2_cleanup(channel_tech.capabilities); + ao2_ref(channel_tech.capabilities, -1); channel_tech.capabilities = NULL; /* Unregister the CLI */ @@ -1538,16 +1654,17 @@ static void public_state_fini(struct public_state* const state) smsdb_atexit(); dev_manager_stop(state); - devices_destroy(state); + devices_finish(state); eventfd_close(&state->dev_manager_event); - AST_RWLIST_HEAD_DESTROY(&state->devices); - - ast_threadpool_shutdown(gpublic->threadpool); + ast_threadpool_shutdown(state->threadpool); } static int unload_module() { + if (!gpublic) { + return 0; + } public_state_fini(gpublic); ast_free(gpublic); gpublic = NULL; diff --git a/src/chan_quectel.h b/src/chan_quectel.h index 5e249b16..9e501029 100644 --- a/src/chan_quectel.h +++ b/src/chan_quectel.h @@ -16,8 +16,8 @@ #include "ast_config.h" +#include #include -#include #include #include #include @@ -91,9 +91,6 @@ typedef struct pvt_stat { struct at_queue_task; typedef struct pvt { - AST_LIST_ENTRY(pvt) entry; /*!< linked list pointers */ - - ast_mutex_t lock; /*!< pvt lock */ AST_LIST_HEAD_NOLOCK(, at_queue_task) at_queue; /*!< queue for commands to modem */ AST_LIST_HEAD_NOLOCK(, cpvt) chans; /*!< list of channels */ @@ -195,7 +192,7 @@ typedef struct pvt { #define PVT_STAT(pvt, name) PVT_STAT_T(&(pvt)->stat, name) typedef struct public_state { - AST_RWLIST_HEAD(devices, pvt) devices; + struct ao2_container* pvts; struct ast_threadpool* threadpool; pthread_t dev_manager_thread; int dev_manager_event; diff --git a/src/channel.c b/src/channel.c index 873f2b9f..a33d2d62 100644 --- a/src/channel.c +++ b/src/channel.c @@ -240,7 +240,7 @@ static int channel_call(struct ast_channel* channel, const char* dest, attribute return -1; } - SCOPED_MUTEX(pvt_lock, &pvt->lock); + SCOPED_AO2LOCK(pvt_lock, pvt); // FIXME: check if bridged on same device with CALL_FLAG_HOLD_OTHER if (!pvt_ready4voice_call(pvt, cpvt, opts)) { @@ -285,7 +285,7 @@ static int channel_hangup(struct ast_channel* channel) if (cpvt && cpvt->channel == channel && cpvt->pvt) { struct pvt* const pvt = cpvt->pvt; - SCOPED_MUTEX(pvt_lock, &pvt->lock); + SCOPED_AO2LOCK(pvt_lock, pvt); const int need_hangup = CPVT_TEST_FLAG(cpvt, CALL_FLAG_NEED_HANGUP) ? 1 : 0; const int hangup_cause = ast_channel_hangupcause(channel); @@ -308,10 +308,7 @@ static int channel_hangup(struct ast_channel* channel) /* drop channel -> cpvt reference */ ast_channel_tech_pvt_set(channel, NULL); - - ast_module_unref(self_module()); ast_setstate(channel, AST_STATE_DOWN); - return 0; } @@ -327,7 +324,7 @@ static int channel_answer(struct ast_channel* channel) } struct pvt* const pvt = cpvt->pvt; - SCOPED_MUTEX(pvt_lock, &pvt->lock); + SCOPED_AO2LOCK(pvt_lock, pvt); if (CPVT_DIR_INCOMING(cpvt)) { if (at_enqueue_answer(cpvt)) { @@ -350,7 +347,7 @@ static int channel_digit_begin(struct ast_channel* channel, char digit) } struct pvt* const pvt = cpvt->pvt; - SCOPED_MUTEX(pvt_lock, &pvt->lock); + SCOPED_AO2LOCK(pvt_lock, pvt); const int rv = at_enqueue_dtmf(cpvt, digit); if (rv) { @@ -855,7 +852,7 @@ static int channel_fixup(struct ast_channel* oldchannel, struct ast_channel* new struct pvt* const pvt = cpvt->pvt; - SCOPED_MUTEX(pvt_lock, &pvt->lock); + SCOPED_AO2LOCK(pvt_lock, pvt); if (cpvt->channel == oldchannel) { cpvt->channel = newchannel; @@ -931,7 +928,7 @@ static int channel_indicate(struct ast_channel* channel, int condition, const vo if (!pvt || CONF_SHARED(pvt, moh)) { ast_moh_start(channel, data, NULL); } else { - SCOPED_MUTEX(pvt_lock, &pvt->lock); + SCOPED_AO2LOCK(pvt_lock, pvt); at_enqueue_mute(cpvt, 1); } break; @@ -940,14 +937,14 @@ static int channel_indicate(struct ast_channel* channel, int condition, const vo if (!pvt || CONF_SHARED(pvt, moh)) { ast_moh_stop(channel); } else { - SCOPED_MUTEX(pvt_lock, &pvt->lock); + SCOPED_AO2LOCK(pvt_lock, pvt); at_enqueue_mute(cpvt, 0); } break; case AST_CONTROL_CONNECTED_LINE: { struct ast_party_connected_line* const cnncd = ast_channel_connected(channel); - SCOPED_MUTEX(pvt_lock, &pvt->lock); + SCOPED_AO2LOCK(pvt_lock, pvt); ast_log(LOG_NOTICE, "[%s] Connected party is now %s <%s>\n", PVT_ID(pvt), S_COR(cnncd->id.name.valid, cnncd->id.name.str, ""), S_COR(cnncd->id.number.valid, cnncd->id.number.str, "")); break; @@ -991,6 +988,8 @@ struct ast_channel* channel_new(struct pvt* pvt, int ast_state, const char* cid_ cpvt->channel = channel; pvt->channel_instance++; + ast_channel_stage_snapshot(channel); + ast_channel_tech_pvt_set(channel, cpvt); ast_channel_tech_set(channel, &channel_tech); @@ -998,6 +997,7 @@ struct ast_channel* channel_new(struct pvt* pvt, int ast_state, const char* cid_ struct ast_format_cap* const cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); ast_format_cap_append_by_type(cap, AST_MEDIA_TYPE_TEXT); ast_channel_nativeformats_set(channel, cap); + ao2_ref(cap, -1); } else { struct ast_format* const fmt = (struct ast_format*)pvt_get_audio_format(pvt); #if PTIME_USE_DEFAULT @@ -1009,17 +1009,18 @@ struct ast_channel* channel_new(struct pvt* pvt, int ast_state, const char* cid_ ast_format_cap_append(cap, fmt, ms); ast_format_cap_set_framing(cap, ms); ast_channel_nativeformats_set(channel, cap); + ao2_ref(cap, -1); ast_channel_set_rawreadformat(channel, fmt); ast_channel_set_rawwriteformat(channel, fmt); ast_channel_set_writeformat(channel, fmt); ast_channel_set_readformat(channel, fmt); - } - ast_channel_set_fd(channel, 0, pvt->audio_fd); - if (pvt->a_timer) { - ast_channel_set_fd(channel, 1, ast_timer_fd(pvt->a_timer)); - ast_timer_set_rate(pvt->a_timer, 50); + ast_channel_set_fd(channel, 0, pvt->audio_fd); + if (pvt->a_timer) { + ast_channel_set_fd(channel, 1, ast_timer_fd(pvt->a_timer)); + ast_timer_set_rate(pvt->a_timer, 50); + } } set_channel_vars(pvt, channel); @@ -1032,13 +1033,8 @@ struct ast_channel* channel_new(struct pvt* pvt, int ast_state, const char* cid_ cpvt_change_state(cpvt, (call_state_t)state, AST_CAUSE_NORMAL_UNSPECIFIED); } - ast_module_ref(self_module()); - - /* commit e2630fcd516b8f794bf342d9fd267b0c905e79ce - * Date: Wed Dec 18 19:28:05 2013 +0000a - * ast_channel_alloc() returns allocated channels locked. */ + ast_channel_stage_snapshot_done(channel); ast_channel_unlock(channel); - return channel; } @@ -1117,7 +1113,7 @@ void channel_start_local(struct pvt* pvt, const char* exten, const char* number, struct ast_channel* const channel = ast_request("Local", pvt->local_format_cap ? pvt->local_format_cap : channel_tech.capabilities, NULL, NULL, ast_str_buffer(channel_name), &cause); if (!channel) { - ast_log(LOG_ERROR, "[%s] Unable to request channel Local/%s\n", PVT_ID(pvt), ast_str_buffer(channel_name)); + ast_log(LOG_ERROR, "[%s] Unable to request channel Local/%s: %s [%d]\n", PVT_ID(pvt), ast_str_buffer(channel_name), ast_cause2str(cause), cause); return; } @@ -1128,8 +1124,7 @@ void channel_start_local(struct pvt* pvt, const char* exten, const char* number, setvar_helper(pvt, channel, vars[i].name, vars[i].value); } - cause = ast_pbx_start(channel); - if (cause) { + if (ast_pbx_start(channel)) { ast_hangup(channel); ast_log(LOG_ERROR, "[%s] Unable to start pbx on channel Local/%s\n", PVT_ID(pvt), ast_str_buffer(channel_name)); } diff --git a/src/cli.c b/src/cli.c index e6389998..9329c707 100644 --- a/src/cli.c +++ b/src/cli.c @@ -51,21 +51,26 @@ static char* complete_device(const char* word, int state) { + char* res = NULL; + int which = 0; + const size_t wordlen = strlen(word); struct pvt* pvt; - char* res = NULL; - int which = 0; - int wordlen = strlen(word); - AST_RWLIST_RDLOCK(&gpublic->devices); - AST_RWLIST_TRAVERSE(&gpublic->devices, pvt, entry) { - SCOPED_MUTEX(pvt_lock, &pvt->lock); + struct ao2_iterator i = ao2_iterator_init(gpublic->pvts, 0); + while ((pvt = ao2_iterator_next(&i))) { + if (ao2_lock(pvt)) { + ao2_ref(pvt, -1); + continue; + } if (!strncasecmp(PVT_ID(pvt), word, wordlen) && ++which > state) { res = ast_strdup(PVT_ID(pvt)); + AO2_UNLOCK_AND_UNREF(pvt); break; } - } - AST_RWLIST_UNLOCK(&gpublic->devices); + AO2_UNLOCK_AND_UNREF(pvt); + } + ao2_iterator_destroy(&i); return res; } @@ -87,14 +92,13 @@ static char* cli_show_devices(struct ast_cli_entry* e, int cmd, struct ast_cli_a ast_cli(a->fd, FORMAT1, "ID", "Group", "State", "RSSI", "Mode", "Provider Name", "Model", "Firmware", "Number"); - AST_RWLIST_RDLOCK(&gpublic->devices); - AST_RWLIST_TRAVERSE(&gpublic->devices, pvt, entry) { - SCOPED_MUTEX(pvt_lock, &pvt->lock); + struct ao2_iterator i = ao2_iterator_init(gpublic->pvts, 0); + while ((pvt = ao2_iterator_next(&i))) { + SCOPED_AO2LOCK(pvt_lock, pvt); ast_cli(a->fd, FORMAT2, PVT_ID(pvt), CONF_SHARED(pvt, group), pvt_str_state(pvt), pvt->rssi, pvt->act, pvt->provider_name, pvt->model, pvt->firmware, pvt->imei, pvt->imsi, pvt->subscriber_number); } - AST_RWLIST_UNLOCK(&gpublic->devices); - + ao2_iterator_destroy(&i); return CLI_SUCCESS; } diff --git a/src/cpvt.c b/src/cpvt.c index 78466215..33ca4230 100644 --- a/src/cpvt.c +++ b/src/cpvt.c @@ -14,6 +14,7 @@ #include "at_queue.h" /* struct at_queue_task */ #include "chan_quectel.h" /* struct pvt */ #include "channel.h" +#include "helpers.h" #include "mutils.h" /* ARRAY_LEN() */ const char* call_state2str(call_state_t state) @@ -126,12 +127,15 @@ void cpvt_free(struct cpvt* cpvt) ast_debug(3, "[%s] Destroy cpvt - idx:%d dir:%d state:%s flags:%d channel:%s\n", PVT_ID(pvt), cpvt->call_idx, CPVT_DIRECTION(cpvt), call_state2str(cpvt->state), cpvt->flags, cpvt->channel ? "attached" : "detached"); - if (PVT_NO_CHANS(pvt)) { - pvt_on_remove_last_channel(pvt); - pvt_try_restate(pvt); + if (!CPVT_TEST_FLAG(cpvt, CALL_FLAG_DISCONNECTING)) { + if (PVT_NO_CHANS(pvt)) { + pvt_on_remove_last_channel(pvt); + pvt_try_restate(pvt); + } + + decrease_chan_counters(cpvt, pvt); } - decrease_chan_counters(cpvt, pvt); relink_to_sys_chan(cpvt, pvt); ast_free(cpvt->buffer); @@ -370,14 +374,14 @@ int cpvt_change_state(struct cpvt* const cpvt, call_state_t newstate, int cause) return 1; } -void cpvt_lock(struct cpvt* const cpvt) +int cpvt_lock(struct cpvt* const cpvt) { struct pvt* const pvt = cpvt->pvt; if (!pvt) { - return; + return -1; } - ast_mutex_trylock(&pvt->lock); + return AO2_REF_AND_LOCK(pvt); } void cpvt_try_lock(struct cpvt* const cpvt) @@ -392,9 +396,9 @@ void cpvt_try_lock(struct cpvt* const cpvt) return; } - ast_mutex_t* const mutex = &pvt->lock; + ao2_ref(pvt, 1); - while (ast_mutex_trylock(mutex)) { + while (ao2_trylock(pvt)) { CHANNEL_DEADLOCK_AVOIDANCE(channel); } } diff --git a/src/cpvt.h b/src/cpvt.h index ffcb2df8..7cb789fb 100644 --- a/src/cpvt.h +++ b/src/cpvt.h @@ -47,7 +47,8 @@ typedef enum { CALL_FLAG_MULTIPARTY = 256, /*!< internal, CLCC mpty is 1 */ CALL_FLAG_DIRECTION = 512, /*!< call direction */ CALL_FLAG_LOCAL_CHANNEL = 1024, /*!< local channel flag */ - CALL_FLAG_INTERNAL_REQUEST = 2048 /*!< internal request */ + CALL_FLAG_INTERNAL_REQUEST = 2048, /*!< internal request */ + CALL_FLAG_DISCONNECTING = 4096 /*!< object is disconnecting */ } call_flag_t; #define CALL_DIR_INCOMING 1u @@ -99,7 +100,7 @@ typedef struct cpvt { struct cpvt* cpvt_alloc(struct pvt* pvt, int call_idx, unsigned dir, call_state_t statem, unsigned local_channel); void cpvt_free(struct cpvt* cpvt); -void cpvt_lock(struct cpvt* const); +int cpvt_lock(struct cpvt* const); void cpvt_try_lock(struct cpvt* const); void cpvt_unlock(struct cpvt* const); diff --git a/src/helpers.c b/src/helpers.c index b7d63fd2..1e657a90 100644 --- a/src/helpers.c +++ b/src/helpers.c @@ -48,6 +48,26 @@ int is_valid_phone_number(const char* number) return 1; } +int __ao2_unlock_and_unref(void* obj, const char* file, const char* func, int line, const char* var) +{ + if (__ao2_unlock(obj, file, func, line, var)) { + return 0; + } + __ao2_ref(obj, -1, NULL, file, line, func); + + return 1; +} + +int __ao2_ref_and_lock(void* obj, const char* file, const char* func, int line, const char* var) +{ + __ao2_ref(obj, +1, NULL, file, line, func); + if (__ao2_lock(obj, AO2_LOCK_REQ_MUTEX, file, func, line, var)) { + __ao2_ref(obj, -1, NULL, file, line, func); + return 0; + } + return 1; +} + static struct pvt* get_pvt(const char* dev_name, int online) { struct pvt* const pvt = pvt_find_by_ext(dev_name); @@ -58,7 +78,8 @@ static struct pvt* get_pvt(const char* dev_name, int online) } if (!pvt->connected || (online && !(pvt->initialized && pvt->gsm_registered))) { - ast_mutex_unlock(&pvt->lock); + ao2_unlock(pvt); + ao2_ref(pvt, -1); chan_quectel_err = E_DEVICE_DISABLED; return NULL; } diff --git a/src/helpers.h b/src/helpers.h index 8457d742..5231528f 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -4,12 +4,19 @@ #ifndef CHAN_QUECTEL_HELPERS_H_INCLUDED #define CHAN_QUECTEL_HELPERS_H_INCLUDED +#include #include #include #include "chan_quectel.h" /* restate_time_t */ #include "dc_config.h" /* call_waiting_t */ +int __ao2_unlock_and_unref(void* obj, const char* file, const char* func, int line, const char* var); +#define AO2_UNLOCK_AND_UNREF(a) __ao2_unlock_and_unref(a, __FILE__, __PRETTY_FUNCTION__, __LINE__, #a) + +int __ao2_ref_and_lock(void* obj, const char* file, const char* func, int line, const char* var); +#define AO2_REF_AND_LOCK(a) __ao2_ref_and_lock(a, __FILE__, __PRETTY_FUNCTION__, __LINE__, #a) + /* return status string of sending, status arg is optional */ int send_ussd(const char* dev_name, const char* ussd); int send_sms(const char* const resource, const char* const sca, const char* const destination, const char* const message, int validity, int report); diff --git a/src/monitor_thread.c b/src/monitor_thread.c index 5df7820e..2aeff660 100644 --- a/src/monitor_thread.c +++ b/src/monitor_thread.c @@ -178,7 +178,7 @@ static void monitor_threadproc_pvt(struct pvt* const pvt) RAII_VAR(struct ast_str* const, result, ast_str_create(RINGBUFFER_SIZE), ast_free); - ast_mutex_lock(&pvt->lock); + ao2_lock(pvt); RAII_VAR(char* const, dev, ast_strdup(PVT_ID(pvt)), ast_free); RAII_VAR(struct ast_taskprocessor*, tps, threadpool_serializer(gpublic->threadpool, dev), ast_taskprocessor_unreference); @@ -197,7 +197,7 @@ static void monitor_threadproc_pvt(struct pvt* const pvt) goto e_cleanup; } - ast_mutex_unlock(&pvt->lock); + ao2_unlock(pvt); int read_result = 0; while (1) { @@ -205,7 +205,7 @@ static void monitor_threadproc_pvt(struct pvt* const pvt) ast_debug(5, "[%s] Unable to handle exprired reports\n", dev); } - if (ast_mutex_trylock(&pvt->lock)) { // pvt unlocked + if (ao2_trylock(pvt)) { // pvt unlocked int t = RESPONSE_READ_TIMEOUT; if (!at_wait(fd, &t)) { if (ast_taskprocessor_push(tps, at_enqueue_ping_taskproc, pvt)) { @@ -229,7 +229,7 @@ static void monitor_threadproc_pvt(struct pvt* const pvt) is_cmd_timeout = 0; } - ast_mutex_unlock(&pvt->lock); + ao2_unlock(pvt); if (is_cmd_timeout) { if (t <= 0) { @@ -276,9 +276,9 @@ static void monitor_threadproc_pvt(struct pvt* const pvt) break; } - if (!ast_mutex_trylock(&pvt->lock)) { + if (!ao2_trylock(pvt)) { PVT_STAT(pvt, d_read_bytes) += iovcnt; - ast_mutex_unlock(&pvt->lock); + ao2_unlock(pvt); } struct iovec iov[2]; @@ -303,7 +303,7 @@ static void monitor_threadproc_pvt(struct pvt* const pvt) } } - ast_mutex_lock(&pvt->lock); + ao2_unlock(pvt); e_cleanup: if (!pvt->initialized) { @@ -315,21 +315,22 @@ static void monitor_threadproc_pvt(struct pvt* const pvt) e_restart: pvt_disconnect(pvt); - // pvt->monitor_running = 0; - ast_mutex_unlock(&pvt->lock); + ao2_unlock(pvt); } static void* monitor_threadproc(void* _pvt) { struct pvt* const pvt = _pvt; monitor_threadproc_pvt(pvt); - /* TODO: wakeup discovery thread after some delay */ + ao2_ref(pvt, -1); return NULL; } int pvt_monitor_start(struct pvt* pvt) { + ao2_ref(pvt, 1); if (ast_pthread_create_background(&pvt->monitor_thread, NULL, monitor_threadproc, pvt) < 0) { + ao2_ref(pvt, -1); pvt->monitor_thread = AST_PTHREADT_NULL; return 0; } @@ -348,7 +349,7 @@ void pvt_monitor_stop(struct pvt* pvt) { const pthread_t id = pvt->monitor_thread; - SCOPED_LOCK(pvt_lock, &pvt->lock, ast_mutex_unlock, ast_mutex_lock); // scoped UNlock + SCOPED_LOCK(pvt_lock, pvt, ao2_unlock, ao2_lock); // scoped UNlock pthread_join(id, NULL); }