From 85723a9e506bda9e21dbf3c06d93d961f10c6b75 Mon Sep 17 00:00:00 2001 From: Richard Mudgett Date: Mon, 2 Oct 2017 17:42:48 -0500 Subject: cdr.c: Add container to key off of Party B channel names. The CDR performance gets worse the further it gets behind in processing stasis messages. One of the reasons is because of a n*m loop used when processing Party B information. * Added a new CDR container that is keyed to Party B so we don't need such a large loop when processing Party B information. NOTE: To reduce the size of the patch I deferred to another patch the renaming of the Party A active_cdrs_by_channel container to active_cdrs_master and renaming the container's hash and cmp functions appropriately. ASTERISK-27335 Change-Id: I0bf66e8868f8adaa4b5dcf9e682e34951c350249 --- main/cdr.c | 415 +++++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 291 insertions(+), 124 deletions(-) diff --git a/main/cdr.c b/main/cdr.c index 611136332..ee1bb366d 100644 --- a/main/cdr.c +++ b/main/cdr.c @@ -351,6 +351,9 @@ static ast_cond_t cdr_pending_cond; /*! \brief A container of the active CDRs indexed by Party A channel id */ static struct ao2_container *active_cdrs_by_channel; +/*! \brief A container of all active CDRs indexed by Party B channel name */ +static struct ao2_container *active_cdrs_all; + /*! \brief Message router for stasis messages regarding channel state */ static struct stasis_message_router *stasis_router; @@ -713,6 +716,7 @@ struct cdr_object { AST_STRING_FIELD(data); /*!< The data for the last accepted application party A was in */ AST_STRING_FIELD(context); /*!< The accepted context for Party A */ AST_STRING_FIELD(exten); /*!< The accepted extension for Party A */ + AST_STRING_FIELD(party_b_name); /*!< Party B channel name. Cached here as it is the all CDRs container key */ ); struct cdr_object *next; /*!< The next CDR object in the chain */ struct cdr_object *last; /*!< The last CDR object in the chain */ @@ -848,6 +852,110 @@ static int cdr_object_channel_cmp_fn(void *obj, void *arg, int flags) return cmp ? 0 : CMP_MATCH; } +/*! + * \internal + * \brief Hash function for all CDR container indexed by Party B channel name. + */ +static int cdr_all_hash_fn(const void *obj, const int flags) +{ + const struct cdr_object *cdr; + const char *key; + + switch (flags & OBJ_SEARCH_MASK) { + case OBJ_SEARCH_KEY: + key = obj; + break; + case OBJ_SEARCH_OBJECT: + cdr = obj; + key = cdr->party_b_name; + break; + default: + ast_assert(0); + return 0; + } + return ast_str_case_hash(key); +} + +/*! + * \internal + * \brief Comparison function for all CDR container indexed by Party B channel name. + */ +static int cdr_all_cmp_fn(void *obj, void *arg, int flags) +{ + struct cdr_object *left = obj; + struct cdr_object *right = arg; + const char *right_key = arg; + int cmp; + + switch (flags & OBJ_SEARCH_MASK) { + case OBJ_SEARCH_OBJECT: + right_key = right->party_b_name; + /* Fall through */ + case OBJ_SEARCH_KEY: + cmp = strcasecmp(left->party_b_name, right_key); + break; + case OBJ_SEARCH_PARTIAL_KEY: + /* + * We could also use a partial key struct containing a length + * so strlen() does not get called for every comparison instead. + */ + cmp = strncasecmp(left->party_b_name, right_key, strlen(right_key)); + break; + default: + /* Sort can only work on something with a full or partial key. */ + ast_assert(0); + cmp = 0; + break; + } + return cmp ? 0 : CMP_MATCH; +} + +/*! + * \internal + * \brief Relink the CDR because Party B's snapshot changed. + * \since 13.19.0 + * + * \return Nothing + */ +static void cdr_all_relink(struct cdr_object *cdr) +{ + ao2_lock(active_cdrs_all); + if (cdr->party_b.snapshot) { + if (strcasecmp(cdr->party_b_name, cdr->party_b.snapshot->name)) { + ao2_unlink_flags(active_cdrs_all, cdr, OBJ_NOLOCK); + ast_string_field_set(cdr, party_b_name, cdr->party_b.snapshot->name); + ao2_link_flags(active_cdrs_all, cdr, OBJ_NOLOCK); + } + } else { + ao2_unlink_flags(active_cdrs_all, cdr, OBJ_NOLOCK); + ast_string_field_set(cdr, party_b_name, ""); + } + ao2_unlock(active_cdrs_all); +} + +/*! + * \internal + * \brief Unlink the master CDR and chained records from the active_cdrs_all container. + * \since 13.19.0 + * + * \return Nothing + */ +static void cdr_all_unlink(struct cdr_object *cdr) +{ + struct cdr_object *cur; + struct cdr_object *next; + + ast_assert(cdr->is_root); + + ao2_lock(active_cdrs_all); + for (cur = cdr->next; cur; cur = next) { + next = cur->next; + ao2_unlink_flags(active_cdrs_all, cur, OBJ_NOLOCK); + ast_string_field_set(cur, party_b_name, ""); + } + ao2_unlock(active_cdrs_all); +} + /*! * \brief \ref cdr_object Destructor */ @@ -1513,6 +1621,7 @@ static int single_state_process_dial_begin(struct cdr_object *cdr, struct ast_ch CDR_DEBUG("%p - Updated Party A %s snapshot\n", cdr, cdr->party_a.snapshot->name); cdr_object_swap_snapshot(&cdr->party_b, peer); + cdr_all_relink(cdr); CDR_DEBUG("%p - Updated Party B %s snapshot\n", cdr, cdr->party_b.snapshot->name); @@ -1560,6 +1669,7 @@ static int single_state_bridge_enter_comparison(struct cdr_object *cdr, CDR_DEBUG("%p - Party A %s has new Party B %s\n", cdr, cdr->party_a.snapshot->name, cand_cdr->party_a.snapshot->name); cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_a); + cdr_all_relink(cdr); if (!cand_cdr->party_b.snapshot) { /* We just stole them - finalize their CDR. Note that this won't * transition their state, it just sets the end time and the @@ -1580,6 +1690,7 @@ static int single_state_bridge_enter_comparison(struct cdr_object *cdr, CDR_DEBUG("%p - Party A %s has new Party B %s\n", cdr, cdr->party_a.snapshot->name, cand_cdr->party_b.snapshot->name); cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_b); + cdr_all_relink(cdr); return 0; } @@ -1732,8 +1843,6 @@ static int dial_state_process_dial_end(struct cdr_object *cdr, struct ast_channe static enum process_bridge_enter_results dial_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel) { - struct ao2_iterator it_cdrs; - char *channel_id; int success = 0; ast_string_field_set(cdr, bridge, bridge->uniqueid); @@ -1745,50 +1854,51 @@ static enum process_bridge_enter_results dial_state_process_bridge_enter(struct return BRIDGE_ENTER_ONLY_PARTY; } - for (it_cdrs = ao2_iterator_init(bridge->channels, 0); - !success && (channel_id = ao2_iterator_next(&it_cdrs)); - ao2_ref(channel_id, -1)) { - struct cdr_object *cand_cdr_master; - struct cdr_object *cand_cdr; + /* If we don't have a Party B (originated channel), skip it */ + if (cdr->party_b.snapshot) { + struct ao2_iterator it_cdrs; + char *channel_id; - cand_cdr_master = ao2_find(active_cdrs_by_channel, channel_id, OBJ_SEARCH_KEY); - if (!cand_cdr_master) { - continue; - } + for (it_cdrs = ao2_iterator_init(bridge->channels, 0); + !success && (channel_id = ao2_iterator_next(&it_cdrs)); + ao2_ref(channel_id, -1)) { + struct cdr_object *cand_cdr_master; + struct cdr_object *cand_cdr; - ao2_lock(cand_cdr_master); - for (cand_cdr = cand_cdr_master; cand_cdr; cand_cdr = cand_cdr->next) { - /* Skip any records that are not in a bridge or in this bridge. - * I'm not sure how that would happen, but it pays to be careful. */ - if (cand_cdr->fn_table != &bridge_state_fn_table || - strcmp(cdr->bridge, cand_cdr->bridge)) { + cand_cdr_master = ao2_find(active_cdrs_by_channel, channel_id, OBJ_SEARCH_KEY); + if (!cand_cdr_master) { continue; } - /* If we don't have a Party B (originated channel), skip it */ - if (!cdr->party_b.snapshot) { - continue; - } + ao2_lock(cand_cdr_master); + for (cand_cdr = cand_cdr_master; cand_cdr; cand_cdr = cand_cdr->next) { + /* Skip any records that are not in a bridge or in this bridge. + * I'm not sure how that would happen, but it pays to be careful. */ + if (cand_cdr->fn_table != &bridge_state_fn_table + || strcmp(cdr->bridge, cand_cdr->bridge)) { + continue; + } - /* Skip any records that aren't our Party B */ - if (strcasecmp(cdr->party_b.snapshot->name, cand_cdr->party_a.snapshot->name)) { - continue; - } - cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_a); - /* If they have a Party B, they joined up with someone else as their - * Party A. Don't finalize them as they're active. Otherwise, we - * have stolen them so they need to be finalized. - */ - if (!cand_cdr->party_b.snapshot) { - cdr_object_finalize(cand_cdr); + /* Skip any records that aren't our Party B */ + if (strcasecmp(cdr->party_b.snapshot->name, cand_cdr->party_a.snapshot->name)) { + continue; + } + cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_a); + /* If they have a Party B, they joined up with someone else as their + * Party A. Don't finalize them as they're active. Otherwise, we + * have stolen them so they need to be finalized. + */ + if (!cand_cdr->party_b.snapshot) { + cdr_object_finalize(cand_cdr); + } + success = 1; + break; } - success = 1; - break; + ao2_unlock(cand_cdr_master); + ao2_cleanup(cand_cdr_master); } - ao2_unlock(cand_cdr_master); - ao2_cleanup(cand_cdr_master); + ao2_iterator_destroy(&it_cdrs); } - ao2_iterator_destroy(&it_cdrs); /* We always transition state, even if we didn't get a peer */ cdr_object_transition_state(cdr, &bridge_state_fn_table); @@ -2029,38 +2139,54 @@ static void handle_dial_message(void *data, struct stasis_subscription *sub, str ao2_cleanup(cdr); } -static int cdr_object_finalize_party_b(void *obj, void *arg, int flags) +static int cdr_object_finalize_party_b(void *obj, void *arg, void *data, int flags) { struct cdr_object *cdr = obj; - struct ast_channel_snapshot *party_b = arg; - struct cdr_object *it_cdr; - for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { - if (it_cdr->party_b.snapshot - && !strcasecmp(it_cdr->party_b.snapshot->name, party_b->name)) { - /* Don't transition to the finalized state - let the Party A do - * that when its ready - */ - cdr_object_finalize(it_cdr); - } + if (!strcasecmp(cdr->party_b_name, arg)) { +#ifdef AST_DEVMODE + struct ast_channel_snapshot *party_b = data; + + /* + * For sanity's sake we also assert the party_b snapshot + * is consistent with the key. + */ + ast_assert(cdr->party_b.snapshot + && !strcasecmp(cdr->party_b.snapshot->name, party_b->name)); +#endif + + /* Don't transition to the finalized state - let the Party A do + * that when its ready + */ + cdr_object_finalize(cdr); } return 0; } -static int cdr_object_update_party_b(void *obj, void *arg, int flags) +static int cdr_object_update_party_b(void *obj, void *arg, void *data, int flags) { struct cdr_object *cdr = obj; - struct ast_channel_snapshot *party_b = arg; - struct cdr_object *it_cdr; - for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { - if (!it_cdr->fn_table->process_party_b) { - continue; - } - if (it_cdr->party_b.snapshot - && !strcasecmp(it_cdr->party_b.snapshot->name, party_b->name)) { - it_cdr->fn_table->process_party_b(it_cdr, party_b); + if (cdr->fn_table->process_party_b + && !strcasecmp(cdr->party_b_name, arg)) { + struct ast_channel_snapshot *party_b = data; + + /* + * For sanity's sake we also check the party_b snapshot + * for consistency with the key. The callback needs and + * asserts the snapshot to be this way. + */ + if (!cdr->party_b.snapshot + || strcasecmp(cdr->party_b.snapshot->name, party_b->name)) { + ast_log(LOG_NOTICE, + "CDR for Party A %s(%s) has inconsistent Party B %s name. Message can be ignored but this shouldn't happen.\n", + cdr->linkedid, + cdr->party_a.snapshot->name, + cdr->party_b_name); + return 0; } + + cdr->fn_table->process_party_b(cdr, party_b); } return 0; } @@ -2134,44 +2260,46 @@ static void handle_channel_cache_message(void *data, struct stasis_subscription name = new_snapshot ? new_snapshot->name : old_snapshot->name; ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", name); ast_assert(0); - } else { - ao2_lock(cdr); - if (new_snapshot) { - int all_reject = 1; + } else if (new_snapshot) { + int all_reject = 1; - for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { - if (!it_cdr->fn_table->process_party_a) { - continue; - } - all_reject &= it_cdr->fn_table->process_party_a(it_cdr, new_snapshot); + ao2_lock(cdr); + for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { + if (!it_cdr->fn_table->process_party_a) { + continue; } - if (all_reject && check_new_cdr_needed(old_snapshot, new_snapshot)) { - /* We're not hung up and we have a new snapshot - we need a new CDR */ - struct cdr_object *new_cdr; + all_reject &= it_cdr->fn_table->process_party_a(it_cdr, new_snapshot); + } + if (all_reject && check_new_cdr_needed(old_snapshot, new_snapshot)) { + /* We're not hung up and we have a new snapshot - we need a new CDR */ + struct cdr_object *new_cdr; - new_cdr = cdr_object_create_and_append(cdr); - if (new_cdr) { - new_cdr->fn_table->process_party_a(new_cdr, new_snapshot); - } - } - } else { - CDR_DEBUG("%p - Beginning finalize/dispatch for %s\n", cdr, old_snapshot->name); - for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { - cdr_object_finalize(it_cdr); + new_cdr = cdr_object_create_and_append(cdr); + if (new_cdr) { + new_cdr->fn_table->process_party_a(new_cdr, new_snapshot); } - cdr_object_dispatch(cdr); - ao2_unlink(active_cdrs_by_channel, cdr); } ao2_unlock(cdr); + } else { + ao2_lock(cdr); + CDR_DEBUG("%p - Beginning finalize/dispatch for %s\n", cdr, old_snapshot->name); + for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { + cdr_object_finalize(it_cdr); + } + cdr_object_dispatch(cdr); + ao2_unlock(cdr); + + cdr_all_unlink(cdr); + ao2_unlink(active_cdrs_by_channel, cdr); } /* Handle Party B */ if (new_snapshot) { - ao2_callback(active_cdrs_by_channel, OBJ_NODATA, cdr_object_update_party_b, - new_snapshot); + ao2_callback_data(active_cdrs_all, OBJ_NODATA | OBJ_MULTIPLE | OBJ_SEARCH_KEY, + cdr_object_update_party_b, (char *) new_snapshot->name, new_snapshot); } else { - ao2_callback(active_cdrs_by_channel, OBJ_NODATA, cdr_object_finalize_party_b, - old_snapshot); + ao2_callback_data(active_cdrs_all, OBJ_NODATA | OBJ_MULTIPLE | OBJ_SEARCH_KEY, + cdr_object_finalize_party_b, (char *) old_snapshot->name, old_snapshot); } ao2_cleanup(cdr); @@ -2183,29 +2311,25 @@ struct bridge_leave_data { }; /*! \brief Callback used to notify CDRs of a Party B leaving the bridge */ -static int cdr_object_party_b_left_bridge_cb(void *obj, void *arg, int flags) +static int cdr_object_party_b_left_bridge_cb(void *obj, void *arg, void *data, int flags) { struct cdr_object *cdr = obj; - struct bridge_leave_data *leave_data = arg; - struct cdr_object *it_cdr; + struct bridge_leave_data *leave_data = data; + + if (cdr->fn_table == &bridge_state_fn_table + && !strcmp(cdr->bridge, leave_data->bridge->uniqueid) + && !strcasecmp(cdr->party_b_name, arg)) { + /* + * For sanity's sake we also assert the party_b snapshot + * is consistent with the key. + */ + ast_assert(cdr->party_b.snapshot + && !strcasecmp(cdr->party_b.snapshot->name, leave_data->channel->name)); - if (strcmp(cdr->bridge, leave_data->bridge->uniqueid)) { - return 0; - } - for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { - if (it_cdr->fn_table != &bridge_state_fn_table) { - continue; - } - if (!it_cdr->party_b.snapshot) { - continue; - } - if (strcasecmp(it_cdr->party_b.snapshot->name, leave_data->channel->name)) { - continue; - } /* It is our Party B, in our bridge. Set the end time and let the handler * transition our CDR appropriately when we leave the bridge. */ - cdr_object_finalize(it_cdr); + cdr_object_finalize(cdr); } return 0; } @@ -2281,8 +2405,8 @@ static void handle_bridge_leave_message(void *data, struct stasis_subscription * /* Party B */ if (left_bridge && strcmp(bridge->subclass, "parking")) { - ao2_callback(active_cdrs_by_channel, OBJ_NODATA, - cdr_object_party_b_left_bridge_cb, + ao2_callback_data(active_cdrs_all, OBJ_NODATA | OBJ_MULTIPLE | OBJ_SEARCH_KEY, + cdr_object_party_b_left_bridge_cb, (char *) leave_data.channel->name, &leave_data); } @@ -2305,6 +2429,7 @@ static void bridge_candidate_add_to_cdr(struct cdr_object *cdr, return; } cdr_object_snapshot_copy(&new_cdr->party_b, party_b); + cdr_all_relink(new_cdr); cdr_object_check_party_a_answer(new_cdr); ast_string_field_set(new_cdr, bridge, cdr->bridge); cdr_object_transition_state(new_cdr, &bridge_state_fn_table); @@ -2363,6 +2488,7 @@ static int bridge_candidate_process(struct cdr_object *cdr, struct cdr_object *b cand_cdr, cand_cdr->party_a.snapshot->name, cdr->party_a.snapshot->name); cdr_object_snapshot_copy(&cand_cdr->party_b, &cdr->party_a); + cdr_all_relink(cand_cdr); /* It's possible that this joined at one point and was never chosen * as party A. Clear their end time, as it would be set in such a * case. @@ -3239,21 +3365,24 @@ struct party_b_userfield_update { }; /*! \brief Callback used to update the userfield on Party B on all CDRs */ -static int cdr_object_update_party_b_userfield_cb(void *obj, void *arg, int flags) +static int cdr_object_update_party_b_userfield_cb(void *obj, void *arg, void *data, int flags) { struct cdr_object *cdr = obj; - struct party_b_userfield_update *info = arg; - struct cdr_object *it_cdr; - for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { - if (it_cdr->fn_table == &finalized_state_fn_table && it_cdr->next != NULL) { - continue; - } - if (it_cdr->party_b.snapshot - && !strcasecmp(it_cdr->party_b.snapshot->name, info->channel_name)) { - strcpy(it_cdr->party_b.userfield, info->userfield); - } + if ((cdr->fn_table != &finalized_state_fn_table || !cdr->next) + && !strcasecmp(cdr->party_b_name, arg)) { + struct party_b_userfield_update *info = data; + + /* + * For sanity's sake we also assert the party_b snapshot + * is consistent with the key. + */ + ast_assert(cdr->party_b.snapshot + && !strcasecmp(cdr->party_b.snapshot->name, info->channel_name)); + + strcpy(cdr->party_b.userfield, info->userfield); } + return 0; } @@ -3261,8 +3390,8 @@ void ast_cdr_setuserfield(const char *channel_name, const char *userfield) { struct cdr_object *cdr; struct party_b_userfield_update party_b_info = { - .channel_name = channel_name, - .userfield = userfield, + .channel_name = channel_name, + .userfield = userfield, }; struct cdr_object *it_cdr; @@ -3280,9 +3409,9 @@ void ast_cdr_setuserfield(const char *channel_name, const char *userfield) } /* Handle Party B */ - ao2_callback(active_cdrs_by_channel, OBJ_NODATA, - cdr_object_update_party_b_userfield_cb, - &party_b_info); + ao2_callback_data(active_cdrs_all, OBJ_NODATA | OBJ_MULTIPLE | OBJ_SEARCH_KEY, + cdr_object_update_party_b_userfield_cb, (char *) party_b_info.channel_name, + &party_b_info); ao2_cleanup(cdr); } @@ -3455,6 +3584,7 @@ int ast_cdr_fork(const char *channel_name, struct ast_flags *options) if (cdr_obj->party_b.snapshot) { new_cdr->party_b.snapshot = cdr_obj->party_b.snapshot; ao2_ref(new_cdr->party_b.snapshot, +1); + cdr_all_relink(new_cdr); strcpy(new_cdr->party_b.userfield, cdr_obj->party_b.userfield); new_cdr->party_b.flags = cdr_obj->party_b.flags; if (ast_test_flag(options, AST_CDR_FLAG_KEEP_VARS)) { @@ -4044,7 +4174,9 @@ static int cdr_object_dispatch_all_cb(void *obj, void *arg, int flags) cdr_object_dispatch(cdr); ao2_unlock(cdr); - return 0; + cdr_all_unlink(cdr); + + return CMP_MATCH; } static void finalize_batch_mode(void) @@ -4170,8 +4302,8 @@ static void cdr_engine_shutdown(void) STASIS_MESSAGE_TYPE_CLEANUP(cdr_sync_message_type); - ao2_callback(active_cdrs_by_channel, OBJ_NODATA, cdr_object_dispatch_all_cb, - NULL); + ao2_callback(active_cdrs_by_channel, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, + cdr_object_dispatch_all_cb, NULL); finalize_batch_mode(); ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands)); ast_sched_context_destroy(sched); @@ -4183,8 +4315,12 @@ static void cdr_engine_shutdown(void) ao2_global_obj_release(module_configs); ao2_container_unregister("cdrs_by_channel"); - ao2_ref(active_cdrs_by_channel, -1); + ao2_cleanup(active_cdrs_by_channel); active_cdrs_by_channel = NULL; + + ao2_container_unregister("cdrs_all"); + ao2_cleanup(active_cdrs_all); + active_cdrs_all = NULL; } static void cdr_enable_batch_mode(struct ast_cdr_config *config) @@ -4231,6 +4367,30 @@ static void cdr_container_print_fn(void *v_obj, void *where, ao2_prnt_fn *prnt) } } +/*! + * \internal + * \brief Print all CDR container object. + * \since 13.19.0 + * + * \param v_obj A pointer to the object we want printed. + * \param where User data needed by prnt to determine where to put output. + * \param prnt Print output callback function to use. + * + * \return Nothing + */ +static void cdr_all_print_fn(void *v_obj, void *where, ao2_prnt_fn *prnt) +{ + struct cdr_object *cdr = v_obj; + + if (!cdr) { + return; + } + prnt(where, "Party A: %s; Party B: %s; Bridge %s", + cdr->party_a.snapshot->name, + cdr->party_b.snapshot ? cdr->party_b.snapshot->name : "", + cdr->bridge); +} + /*! * \brief Checks if CDRs are enabled and enables/disables the necessary options */ @@ -4297,6 +4457,13 @@ int ast_cdr_engine_init(void) } ao2_container_register("cdrs_by_channel", active_cdrs_by_channel, cdr_container_print_fn); + active_cdrs_all = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, + NUM_CDR_BUCKETS, cdr_all_hash_fn, NULL, cdr_all_cmp_fn); + if (!active_cdrs_all) { + return -1; + } + ao2_container_register("cdrs_all", active_cdrs_all, cdr_all_print_fn); + sched = ast_sched_context_create(); if (!sched) { ast_log(LOG_ERROR, "Unable to create schedule context.\n"); -- cgit v1.2.3