Merge "lock: Improve performance of DEBUG_THREADS."
[asterisk/asterisk.git] / main / cdr.c
index 02c2710..1c47e24 100644 (file)
@@ -45,8 +45,6 @@
 
 #include "asterisk.h"
 
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
 #include <signal.h>
 #include <inttypes.h>
 
@@ -55,6 +53,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/cdr.h"
 #include "asterisk/callerid.h"
 #include "asterisk/manager.h"
+#include "asterisk/module.h"
 #include "asterisk/causes.h"
 #include "asterisk/linkedlists.h"
 #include "asterisk/utils.h"
@@ -62,7 +61,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/config.h"
 #include "asterisk/cli.h"
 #include "asterisk/stringfields.h"
-#include "asterisk/data.h"
 #include "asterisk/config_options.h"
 #include "asterisk/json.h"
 #include "asterisk/parking.h"
@@ -71,6 +69,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/stasis_bridges.h"
 #include "asterisk/stasis_message_router.h"
 #include "asterisk/astobj2.h"
+#include "asterisk/taskprocessor.h"
 
 /*** DOCUMENTATION
        <configInfo name="cdr" language="en_US">
@@ -98,19 +97,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                        </description>
                                </configOption>
                                <configOption name="unanswered">
-                                       <synopsis>Log calls that are never answered.</synopsis>
-                                       <description><para>Define whether or not to log unanswered calls. Setting this to "yes" will
-                                       report every attempt to ring a phone in dialing attempts, when it was not
-                                       answered. For example, if you try to dial 3 extensions, and this option is "yes",
-                                       you will get 3 CDR's, one for each phone that was rung. Some find this information horribly
-                                       useless. Others find it very valuable. Note, in "yes" mode, you will see one CDR, with one of
-                                       the call targets on one side, and the originating channel on the other, and then one CDR for
-                                       each channel attempted. This may seem redundant, but cannot be helped.</para>
-                                       <para>In brief, this option controls the reporting of unanswered calls which only have an A
-                                       party. Calls which get offered to an outgoing line, but are unanswered, are still
-                                       logged, and that is the intended behavior. (It also results in some B side CDRs being
-                                       output, as they have the B side channel as their source channel, and no destination
-                                       channel.)</para>
+                                       <synopsis>Log calls that are never answered and don't set an outgoing party.</synopsis>
+                                       <description><para>
+                                       Define whether or not to log unanswered calls that don't involve an outgoing party. Setting
+                                       this to "yes" will make calls to extensions that don't answer and don't set a side B channel
+                                       (such as by using the Dial application) receive CDR log entries. If this option is set to
+                                       "no", then those log entries will not be created. Unanswered calls which get offered to an
+                                       outgoing line will always receive log entries regardless of this option, and that is the
+                                       intended behavior.
+                                       </para>
                                        </description>
                                </configOption>
                                <configOption name="congestion">
@@ -129,7 +124,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                                channel. Any statistics are gathered from this new CDR. By enabling
                                                this option, no new CDR is created for the dialplan logic that is
                                                executed in <literal>h</literal> extensions or attached hangup handler
-                                               subroutines. The default value is <literal>no</literal>, indicating
+                                               subroutines. The default value is <literal>yes</literal>, indicating
                                                that a CDR will be generated during hangup logic.</para>
                                        </description>
                                </configOption>
@@ -203,7 +198,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #define DEFAULT_BATCHMODE "0"
 #define DEFAULT_UNANSWERED "0"
 #define DEFAULT_CONGESTION "0"
-#define DEFAULT_END_BEFORE_H_EXTEN "0"
+#define DEFAULT_END_BEFORE_H_EXTEN "1"
 #define DEFAULT_INITIATED_SECONDS "0"
 
 #define DEFAULT_BATCH_SIZE "100"
@@ -213,18 +208,27 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #define DEFAULT_BATCH_SCHEDULER_ONLY "0"
 #define DEFAULT_BATCH_SAFE_SHUTDOWN "1"
 
-#define CDR_DEBUG(mod_cfg, fmt, ...) \
+#define cdr_set_debug_mode(mod_cfg) \
+       do { \
+               cdr_debug_enabled = ast_test_flag(&(mod_cfg)->general->settings, CDR_DEBUG); \
+       } while (0)
+
+static int cdr_debug_enabled;
+
+#define CDR_DEBUG(fmt, ...) \
        do { \
-       if (ast_test_flag(&(mod_cfg)->general->settings, CDR_DEBUG)) { \
-               ast_verb(1, (fmt), ##__VA_ARGS__); \
-       } } while (0)
+               if (cdr_debug_enabled) { \
+                       ast_verbose((fmt), ##__VA_ARGS__); \
+               } \
+       } while (0)
 
 static void cdr_detach(struct ast_cdr *cdr);
 static void cdr_submit_batch(int shutdown);
+static int cdr_toggle_runtime_options(void);
 
 /*! \brief The configuration settings for this module */
 struct module_config {
-       struct ast_cdr_config *general;         /*< CDR global settings */
+       struct ast_cdr_config *general;         /*!< CDR global settings */
 };
 
 /*! \brief The container for the module configuration */
@@ -235,26 +239,60 @@ static struct aco_type general_option = {
        .type = ACO_GLOBAL,
        .name = "general",
        .item_offset = offsetof(struct module_config, general),
-       .category = "^general$",
-       .category_match = ACO_WHITELIST,
+       .category = "general",
+       .category_match = ACO_WHITELIST_EXACT,
+};
+
+/*! Config sections used by existing modules. Do not add to this list. */
+static const char *ignore_categories[] = {
+       "csv",
+       "custom",
+       "manager",
+       "odbc",
+       "pgsql",
+       "radius",
+       "sqlite",
+       "tds",
+       "mysql",
+       NULL,
+};
+
+static struct aco_type ignore_option = {
+       .type = ACO_IGNORE,
+       .name = "modules",
+       .category = (const char*)ignore_categories,
+       .category_match = ACO_WHITELIST_ARRAY,
 };
 
 static void *module_config_alloc(void);
 static void module_config_destructor(void *obj);
+static void module_config_post_apply(void);
 
 /*! \brief The file definition */
 static struct aco_file module_file_conf = {
        .filename = "cdr.conf",
-       .skip_category = "(^csv$|^custom$|^manager$|^odbc$|^pgsql$|^radius$|^sqlite$|^tds$|^mysql$)",
-       .types = ACO_TYPES(&general_option),
+       .types = ACO_TYPES(&general_option, &ignore_option),
 };
 
 CONFIG_INFO_CORE("cdr", cfg_info, module_configs, module_config_alloc,
        .files = ACO_FILES(&module_file_conf),
+       .post_apply_config = module_config_post_apply,
 );
 
 static struct aco_type *general_options[] = ACO_TYPES(&general_option);
 
+static void module_config_post_apply(void)
+{
+       struct module_config *mod_cfg;
+
+       mod_cfg = ao2_global_obj_ref(module_configs);
+       if (!mod_cfg) {
+               return;
+       }
+       cdr_set_debug_mode(mod_cfg);
+       ao2_cleanup(mod_cfg);
+}
+
 /*! \brief Dispose of a module config object */
 static void module_config_destructor(void *obj)
 {
@@ -293,11 +331,15 @@ struct cdr_beitem {
        char desc[80];
        ast_cdrbe be;
        AST_RWLIST_ENTRY(cdr_beitem) list;
+       int suspended:1;
 };
 
 /*! \brief List of registered backends */
 static AST_RWLIST_HEAD_STATIC(be_list, cdr_beitem);
 
+/*! \brief List of registered modifiers */
+static AST_RWLIST_HEAD_STATIC(mo_list, cdr_beitem);
+
 /*! \brief Queued CDR waiting to be batched */
 struct cdr_batch_item {
        struct ast_cdr *cdr;
@@ -327,8 +369,11 @@ AST_MUTEX_DEFINE_STATIC(cdr_batch_lock);
 AST_MUTEX_DEFINE_STATIC(cdr_pending_lock);
 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 the active master CDRs indexed by Party A channel uniqueid */
+static struct ao2_container *active_cdrs_master;
+
+/*! \brief A container of all active CDRs with a Party B 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;
@@ -345,6 +390,9 @@ static struct stasis_forward *parking_subscription;
 /*! \brief The parent topic for all topics we want to aggregate for CDRs */
 static struct stasis_topic *cdr_topic;
 
+/*! \brief A message type used to synchronize with the CDR topic */
+STASIS_MESSAGE_TYPE_DEFN_LOCAL(cdr_sync_message_type);
+
 struct cdr_object;
 
 /*! \brief Return types for \ref process_bridge_enter functions */
@@ -682,14 +730,18 @@ struct cdr_object {
        struct ast_flags flags;                 /*!< Flags on the CDR */
        AST_DECLARE_STRING_FIELDS(
                AST_STRING_FIELD(linkedid);         /*!< Linked ID. Cached here as it may change out from party A, which must be immutable */
-               AST_STRING_FIELD(uniqueid);                     /*!< Unique id of party A. Cached here as it is the primary key of this CDR */
+               AST_STRING_FIELD(uniqueid);                     /*!< Unique id of party A. Cached here as it is the master CDR container key */
                AST_STRING_FIELD(name);             /*!< Channel name of party A. Cached here as the party A address may change */
                AST_STRING_FIELD(bridge);           /*!< The bridge the party A happens to be in. */
                AST_STRING_FIELD(appl);             /*!< The last accepted application party A was in */
                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 */
+       int is_root;                            /*!< True if this is the first CDR in the chain */
 };
 
 /*!
@@ -745,11 +797,7 @@ static void free_variables(struct varshead *headp)
  */
 static void cdr_object_snapshot_copy(struct cdr_object_snapshot *dst, struct cdr_object_snapshot *src)
 {
-       if (dst->snapshot) {
-               ao2_t_ref(dst->snapshot, -1, "release old snapshot during copy");
-       }
-       dst->snapshot = src->snapshot;
-       ao2_t_ref(dst->snapshot, +1, "bump new snapshot during copy");
+       ao2_t_replace(dst->snapshot, src->snapshot, "CDR snapshot copy");
        strcpy(dst->userfield, src->userfield);
        dst->flags = src->flags;
        copy_variables(&dst->variables, &src->variables);
@@ -762,9 +810,7 @@ static void cdr_object_snapshot_copy(struct cdr_object_snapshot *dst, struct cdr
  */
 static void cdr_object_transition_state(struct cdr_object *cdr, struct cdr_object_fn_table *fn_table)
 {
-       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
-
-       CDR_DEBUG(mod_cfg, "%p - Transitioning CDR for %s from state %s to %s\n",
+       CDR_DEBUG("%p - Transitioning CDR for %s from state %s to %s\n",
                cdr, cdr->party_a.snapshot->name,
                cdr->fn_table ? cdr->fn_table->name : "NONE", fn_table->name);
        cdr->fn_table = fn_table;
@@ -772,18 +818,21 @@ static void cdr_object_transition_state(struct cdr_object *cdr, struct cdr_objec
                cdr->fn_table->init_function(cdr);
        }
 }
-/*! \internal
- * \brief Hash function for containers of CDRs indexing by Party A uniqueid */
-static int cdr_object_channel_hash_fn(const void *obj, const int flags)
+
+/*!
+ * \internal
+ * \brief Hash function for master CDR container indexed by Party A uniqueid.
+ */
+static int cdr_master_hash_fn(const void *obj, const int flags)
 {
        const struct cdr_object *cdr;
        const char *key;
 
-       switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
-       case OBJ_KEY:
+       switch (flags & OBJ_SEARCH_MASK) {
+       case OBJ_SEARCH_KEY:
                key = obj;
                break;
-       case OBJ_POINTER:
+       case OBJ_SEARCH_OBJECT:
                cdr = obj;
                key = cdr->uniqueid;
                break;
@@ -794,24 +843,25 @@ static int cdr_object_channel_hash_fn(const void *obj, const int flags)
        return ast_str_case_hash(key);
 }
 
-/*! \internal
- * \brief Comparison function for containers of CDRs indexing by Party A uniqueid
+/*!
+ * \internal
+ * \brief Comparison function for master CDR container indexed by Party A uniqueid.
  */
-static int cdr_object_channel_cmp_fn(void *obj, void *arg, int flags)
+static int cdr_master_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_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
-    case OBJ_POINTER:
+    switch (flags & OBJ_SEARCH_MASK) {
+    case OBJ_SEARCH_OBJECT:
         right_key = right->uniqueid;
         /* Fall through */
-    case OBJ_KEY:
+    case OBJ_SEARCH_KEY:
         cmp = strcmp(left->uniqueid, right_key);
         break;
-    case OBJ_PARTIAL_KEY:
+    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.
@@ -828,6 +878,118 @@ static int cdr_object_channel_cmp_fn(void *obj, void *arg, int flags)
 }
 
 /*!
+ * \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);
+
+       /* Hold a ref to the root CDR to ensure the list members don't go away on us. */
+       ao2_ref(cdr, +1);
+       ao2_lock(active_cdrs_all);
+       for (cur = cdr; cur; cur = next) {
+               next = cur->next;
+               ao2_unlink_flags(active_cdrs_all, cur, OBJ_NOLOCK);
+               /*
+                * It is safe to still use cur after unlinking because the
+                * root CDR holds a ref to all the CDRs in the list and we
+                * have a ref to the root CDR.
+                */
+               ast_string_field_set(cur, party_b_name, "");
+       }
+       ao2_unlock(active_cdrs_all);
+       ao2_ref(cdr, -1);
+}
+
+/*!
  * \brief \ref cdr_object Destructor
  */
 static void cdr_object_dtor(void *obj)
@@ -845,7 +1007,22 @@ static void cdr_object_dtor(void *obj)
        }
        ast_string_field_free_memory(cdr);
 
-       ao2_cleanup(cdr->next);
+       /* CDR destruction used to work by calling ao2_cleanup(next) and
+        * allowing the chain to destroy itself neatly. Unfortunately, for
+        * really long chains, this can result in a stack overflow. So now
+        * when the root CDR is destroyed, it is responsible for unreffing
+        * all CDRs in the chain
+        */
+       if (cdr->is_root) {
+               struct cdr_object *curr = cdr->next;
+               struct cdr_object *next;
+
+               while (curr) {
+                       next = curr->next;
+                       ao2_cleanup(curr);
+                       curr = next;
+               }
+       }
 }
 
 /*!
@@ -857,7 +1034,6 @@ static void cdr_object_dtor(void *obj)
  */
 static struct cdr_object *cdr_object_alloc(struct ast_channel_snapshot *chan)
 {
-       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
        struct cdr_object *cdr;
 
        ast_assert(chan != NULL);
@@ -880,7 +1056,7 @@ static struct cdr_object *cdr_object_alloc(struct ast_channel_snapshot *chan)
        cdr->party_a.snapshot = chan;
        ao2_t_ref(cdr->party_a.snapshot, +1, "bump snapshot during CDR creation");
 
-       CDR_DEBUG(mod_cfg, "%p - Created CDR for channel %s\n", cdr, chan->name);
+       CDR_DEBUG("%p - Created CDR for channel %s\n", cdr, chan->name);
 
        cdr_object_transition_state(cdr, &single_state_fn_table);
 
@@ -908,6 +1084,16 @@ static struct cdr_object *cdr_object_create_and_append(struct cdr_object *cdr)
        ast_string_field_set(new_cdr, linkedid, cdr_last->linkedid);
        ast_string_field_set(new_cdr, appl, cdr_last->appl);
        ast_string_field_set(new_cdr, data, cdr_last->data);
+       ast_string_field_set(new_cdr, context, cdr_last->context);
+       ast_string_field_set(new_cdr, exten, cdr_last->exten);
+
+       /*
+        * If the current CDR says to disable all future ones,
+        * keep the disable chain going
+        */
+       if (ast_test_flag(&cdr_last->flags, AST_CDR_FLAG_DISABLE_ALL)) {
+               ast_set_flag(&new_cdr->flags, AST_CDR_FLAG_DISABLE_ALL);
+       }
 
        /* Copy over other Party A information */
        cdr_object_snapshot_copy(&new_cdr->party_a, &cdr_last->party_a);
@@ -923,6 +1109,28 @@ static struct cdr_object *cdr_object_create_and_append(struct cdr_object *cdr)
 }
 
 /*!
+ * \internal
+ * \brief Determine if CDR flag is configured.
+ *
+ * \param cdr_flag The configured CDR flag to check.
+ *
+ * \retval 0 if the CDR flag is not configured.
+ * \retval non-zero if the CDR flag is configured.
+ *
+ * \return Nothing
+ */
+static int is_cdr_flag_set(unsigned int cdr_flag)
+{
+       struct module_config *mod_cfg;
+       int flag_set;
+
+       mod_cfg = ao2_global_obj_ref(module_configs);
+       flag_set = mod_cfg && ast_test_flag(&mod_cfg->general->settings, cdr_flag);
+       ao2_cleanup(mod_cfg);
+       return flag_set;
+}
+
+/*!
  * \brief Return whether or not a channel has changed its state in the dialplan, subject
  * to endbeforehexten logic
  *
@@ -935,12 +1143,9 @@ static struct cdr_object *cdr_object_create_and_append(struct cdr_object *cdr)
 static int snapshot_cep_changed(struct ast_channel_snapshot *old_snapshot,
        struct ast_channel_snapshot *new_snapshot)
 {
-       RAII_VAR(struct module_config *, mod_cfg,
-               ao2_global_obj_ref(module_configs), ao2_cleanup);
-
        /* If we ignore hangup logic, don't indicate that we're executing anything new */
-       if (ast_test_flag(&mod_cfg->general->settings, CDR_END_BEFORE_H_EXTEN)
-               && ast_test_flag(&new_snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)) {
+       if (ast_test_flag(&new_snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)
+               && is_cdr_flag_set(CDR_END_BEFORE_H_EXTEN)) {
                return 0;
        }
 
@@ -948,10 +1153,11 @@ static int snapshot_cep_changed(struct ast_channel_snapshot *old_snapshot,
         * will attempt to clear the application and restore the dummy originate application
         * of "AppDialX". Ignore application changes to AppDialX as a result.
         */
-       if (strcmp(new_snapshot->appl, old_snapshot->appl) && strncasecmp(new_snapshot->appl, "appdial", 7)
+       if (strcmp(new_snapshot->appl, old_snapshot->appl)
+               && strncasecmp(new_snapshot->appl, "appdial", 7)
                && (strcmp(new_snapshot->context, old_snapshot->context)
-               || strcmp(new_snapshot->exten, old_snapshot->exten)
-               || new_snapshot->priority != old_snapshot->priority)) {
+                       || strcmp(new_snapshot->exten, old_snapshot->exten)
+                       || new_snapshot->priority != old_snapshot->priority)) {
                return 1;
        }
 
@@ -1023,15 +1229,15 @@ static long cdr_object_get_duration(struct cdr_object *cdr)
  */
 static long cdr_object_get_billsec(struct cdr_object *cdr)
 {
-       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
        long int ms;
 
        if (ast_tvzero(cdr->answer)) {
                return 0;
        }
+
        ms = ast_tvdiff_ms(ast_tvzero(cdr->end) ? ast_tvnow() : cdr->end, cdr->answer);
-       if (ast_test_flag(&mod_cfg->general->settings, CDR_INITIATED_SECONDS)
-               && (ms % 1000 >= 500)) {
+       if (ms % 1000 >= 500
+               && is_cdr_flag_set(CDR_INITIATED_SECONDS)) {
                ms = (ms / 1000) + 1;
        } else {
                ms = ms / 1000;
@@ -1080,14 +1286,13 @@ static struct ast_cdr *cdr_object_create_public_records(struct cdr_object *cdr)
        struct ast_var_t *it_var, *it_copy_var;
        struct ast_channel_snapshot *party_a;
        struct ast_channel_snapshot *party_b;
-       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
 
        for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
                struct ast_cdr *cdr_copy;
 
                /* Don't create records for CDRs where the party A was a dialed channel */
                if (snapshot_is_dialed(it_cdr->party_a.snapshot) && !it_cdr->party_b.snapshot) {
-                       CDR_DEBUG(mod_cfg, "%p - %s is dialed and has no Party B; discarding\n", it_cdr,
+                       ast_debug(1, "CDR for %s is dialed and has no Party B; discarding\n",
                                it_cdr->party_a.snapshot->name);
                        continue;
                }
@@ -1111,8 +1316,8 @@ static struct ast_cdr *cdr_object_create_public_records(struct cdr_object *cdr)
                ast_copy_string(cdr_copy->uniqueid, party_a->uniqueid, sizeof(cdr_copy->uniqueid));
                ast_copy_string(cdr_copy->lastapp, it_cdr->appl, sizeof(cdr_copy->lastapp));
                ast_copy_string(cdr_copy->lastdata, it_cdr->data, sizeof(cdr_copy->lastdata));
-               ast_copy_string(cdr_copy->dst, party_a->exten, sizeof(cdr_copy->dst));
-               ast_copy_string(cdr_copy->dcontext, party_a->context, sizeof(cdr_copy->dcontext));
+               ast_copy_string(cdr_copy->dst, it_cdr->exten, sizeof(cdr_copy->dst));
+               ast_copy_string(cdr_copy->dcontext, it_cdr->context, sizeof(cdr_copy->dcontext));
 
                /* Party B */
                if (party_b) {
@@ -1175,13 +1380,11 @@ static struct ast_cdr *cdr_object_create_public_records(struct cdr_object *cdr)
  */
 static void cdr_object_dispatch(struct cdr_object *cdr)
 {
-       RAII_VAR(struct module_config *, mod_cfg,
-                       ao2_global_obj_ref(module_configs), ao2_cleanup);
        struct ast_cdr *pub_cdr;
 
-       CDR_DEBUG(mod_cfg, "%p - Dispatching CDR for Party A %s, Party B %s\n", cdr,
-                       cdr->party_a.snapshot->name,
-                       cdr->party_b.snapshot ? cdr->party_b.snapshot->name : "<none>");
+       CDR_DEBUG("%p - Dispatching CDR for Party A %s, Party B %s\n", cdr,
+               cdr->party_a.snapshot->name,
+               cdr->party_b.snapshot ? cdr->party_b.snapshot->name : "<none>");
        pub_cdr = cdr_object_create_public_records(cdr);
        cdr_detach(pub_cdr);
 }
@@ -1193,16 +1396,13 @@ static void cdr_object_dispatch(struct cdr_object *cdr)
  */
 static void cdr_object_set_disposition(struct cdr_object *cdr, int hangupcause)
 {
-       RAII_VAR(struct module_config *, mod_cfg,
-                       ao2_global_obj_ref(module_configs), ao2_cleanup);
-
        /* Change the disposition based on the hang up cause */
        switch (hangupcause) {
        case AST_CAUSE_BUSY:
                cdr->disposition = AST_CDR_BUSY;
                break;
        case AST_CAUSE_CONGESTION:
-               if (!ast_test_flag(&mod_cfg->general->settings, CDR_CONGESTION)) {
+               if (!is_cdr_flag_set(CDR_CONGESTION)) {
                        cdr->disposition = AST_CDR_FAILED;
                } else {
                        cdr->disposition = AST_CDR_CONGESTION;
@@ -1234,9 +1434,6 @@ static void cdr_object_set_disposition(struct cdr_object *cdr, int hangupcause)
  */
 static void cdr_object_finalize(struct cdr_object *cdr)
 {
-       RAII_VAR(struct module_config *, mod_cfg,
-                       ao2_global_obj_ref(module_configs), ao2_cleanup);
-
        if (!ast_tvzero(cdr->end)) {
                return;
        }
@@ -1257,11 +1454,11 @@ static void cdr_object_finalize(struct cdr_object *cdr)
        /* tv_usec is suseconds_t, which could be int or long */
        ast_debug(1, "Finalized CDR for %s - start %ld.%06ld answer %ld.%06ld end %ld.%06ld dispo %s\n",
                        cdr->party_a.snapshot->name,
-                       cdr->start.tv_sec,
+                       (long)cdr->start.tv_sec,
                        (long)cdr->start.tv_usec,
-                       cdr->answer.tv_sec,
+                       (long)cdr->answer.tv_sec,
                        (long)cdr->answer.tv_usec,
-                       cdr->end.tv_sec,
+                       (long)cdr->end.tv_sec,
                        (long)cdr->end.tv_usec,
                        ast_cdr_disp2str(cdr->disposition));
 }
@@ -1272,10 +1469,8 @@ static void cdr_object_finalize(struct cdr_object *cdr)
  */
 static void cdr_object_check_party_a_hangup(struct cdr_object *cdr)
 {
-       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
-
-       if (ast_test_flag(&mod_cfg->general->settings, CDR_END_BEFORE_H_EXTEN)
-               && ast_test_flag(&cdr->party_a.snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)) {
+       if (ast_test_flag(&cdr->party_a.snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)
+               && is_cdr_flag_set(CDR_END_BEFORE_H_EXTEN)) {
                cdr_object_finalize(cdr);
        }
 
@@ -1289,14 +1484,13 @@ static void cdr_object_check_party_a_hangup(struct cdr_object *cdr)
  * \brief Check to see if a CDR needs to be answered based on its Party A.
  * Note that this is safe to call as much as you want - we won't answer twice
  */
-static void cdr_object_check_party_a_answer(struct cdr_object *cdr) {
-       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
-
+static void cdr_object_check_party_a_answer(struct cdr_object *cdr)
+{
        if (cdr->party_a.snapshot->state == AST_STATE_UP && ast_tvzero(cdr->answer)) {
                cdr->answer = ast_tvnow();
                /* tv_usec is suseconds_t, which could be int or long */
-               CDR_DEBUG(mod_cfg, "%p - Set answered time to %ld.%06ld\n", cdr,
-                       cdr->answer.tv_sec,
+               CDR_DEBUG("%p - Set answered time to %ld.%06ld\n", cdr,
+                       (long)cdr->answer.tv_sec,
                        (long)cdr->answer.tv_usec);
        }
 }
@@ -1310,13 +1504,13 @@ static void cdr_object_update_cid(struct cdr_object_snapshot *old_snapshot, stru
                set_variable(&old_snapshot->variables, "calledsubaddr", new_snapshot->dialed_subaddr);
                return;
        }
-       if (!strcmp(old_snapshot->snapshot->caller_dnid, new_snapshot->caller_dnid)) {
+       if (strcmp(old_snapshot->snapshot->caller_dnid, new_snapshot->caller_dnid)) {
                set_variable(&old_snapshot->variables, "dnid", new_snapshot->caller_dnid);
        }
-       if (!strcmp(old_snapshot->snapshot->caller_subaddr, new_snapshot->caller_subaddr)) {
+       if (strcmp(old_snapshot->snapshot->caller_subaddr, new_snapshot->caller_subaddr)) {
                set_variable(&old_snapshot->variables, "callingsubaddr", new_snapshot->caller_subaddr);
        }
-       if (!strcmp(old_snapshot->snapshot->dialed_subaddr, new_snapshot->dialed_subaddr)) {
+       if (strcmp(old_snapshot->snapshot->dialed_subaddr, new_snapshot->dialed_subaddr)) {
                set_variable(&old_snapshot->variables, "calledsubaddr", new_snapshot->dialed_subaddr);
        }
 }
@@ -1331,20 +1525,36 @@ static void cdr_object_swap_snapshot(struct cdr_object_snapshot *old_snapshot,
                struct ast_channel_snapshot *new_snapshot)
 {
        cdr_object_update_cid(old_snapshot, new_snapshot);
-       if (old_snapshot->snapshot) {
-               ao2_t_ref(old_snapshot->snapshot, -1, "Drop ref for swap");
-       }
-       ao2_t_ref(new_snapshot, +1, "Bump ref for swap");
-       old_snapshot->snapshot = new_snapshot;
+       ao2_t_replace(old_snapshot->snapshot, new_snapshot, "Swap CDR shapshot");
 }
 
 /* BASE METHOD IMPLEMENTATIONS */
 
 static int base_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
 {
-       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
-
        ast_assert(strcasecmp(snapshot->name, cdr->party_a.snapshot->name) == 0);
+
+       /* Finalize the CDR if we're in hangup logic and we're set to do so */
+       if (ast_test_flag(&snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)
+               && is_cdr_flag_set(CDR_END_BEFORE_H_EXTEN)) {
+               cdr_object_finalize(cdr);
+               return 0;
+       }
+
+       /*
+        * Only record the context and extension if we aren't in a subroutine, or if
+        * we are executing hangup logic.
+        */
+       if (!ast_test_flag(&snapshot->flags, AST_FLAG_SUBROUTINE_EXEC)
+               || ast_test_flag(&snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)) {
+               if (strcmp(cdr->context, snapshot->context)) {
+                       ast_string_field_set(cdr, context, snapshot->context);
+               }
+               if (strcmp(cdr->exten, snapshot->exten)) {
+                       ast_string_field_set(cdr, exten, snapshot->exten);
+               }
+       }
+
        cdr_object_swap_snapshot(&cdr->party_a, snapshot);
 
        /* When Party A is originated to an application and the application exits, the stack
@@ -1352,14 +1562,29 @@ static int base_process_party_a(struct cdr_object *cdr, struct ast_channel_snaps
         * of "AppDialX". Prevent that, and any other application changes we might not want
         * here.
         */
-       if (!ast_strlen_zero(snapshot->appl)
-                       && (strncasecmp(snapshot->appl, "appdial", 7) || ast_strlen_zero(cdr->appl))
-                       && !ast_test_flag(&cdr->flags, AST_CDR_LOCK_APP)) {
-               ast_string_field_set(cdr, appl, snapshot->appl);
-               ast_string_field_set(cdr, data, snapshot->data);
+       if (!ast_test_flag(&cdr->flags, AST_CDR_LOCK_APP)
+               && !ast_strlen_zero(snapshot->appl)
+               && (strncasecmp(snapshot->appl, "appdial", 7) || ast_strlen_zero(cdr->appl))) {
+               if (strcmp(cdr->appl, snapshot->appl)) {
+                       ast_string_field_set(cdr, appl, snapshot->appl);
+               }
+               if (strcmp(cdr->data, snapshot->data)) {
+                       ast_string_field_set(cdr, data, snapshot->data);
+               }
+
+               /* Dial (app_dial) is a special case. Because pre-dial handlers, which
+                * execute before the dial begins, will alter the application/data to
+                * something people typically don't want to see, if we see a channel enter
+                * into Dial here, we set the appl/data accordingly and lock it.
+                */
+               if (!strcmp(snapshot->appl, "Dial")) {
+                       ast_set_flag(&cdr->flags, AST_CDR_LOCK_APP);
+               }
        }
 
-       ast_string_field_set(cdr, linkedid, snapshot->linkedid);
+       if (strcmp(cdr->linkedid, snapshot->linkedid)) {
+               ast_string_field_set(cdr, linkedid, snapshot->linkedid);
+       }
        cdr_object_check_party_a_answer(cdr);
        cdr_object_check_party_a_hangup(cdr);
 
@@ -1368,15 +1593,11 @@ static int base_process_party_a(struct cdr_object *cdr, struct ast_channel_snaps
 
 static int base_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
 {
-       /* In general, most things shouldn't get a bridge leave */
-       ast_assert(0);
-       return 1;
+       return 0;
 }
 
 static int base_process_dial_end(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer, const char *dial_status)
 {
-       /* In general, most things shouldn't get a dial end. */
-       ast_assert(0);
        return 0;
 }
 
@@ -1408,7 +1629,8 @@ static int base_process_parked_channel(struct cdr_object *cdr, struct ast_parked
 
 /* SINGLE STATE */
 
-static void single_state_init_function(struct cdr_object *cdr) {
+static void single_state_init_function(struct cdr_object *cdr)
+{
        cdr->start = ast_tvnow();
        cdr_object_check_party_a_answer(cdr);
 }
@@ -1423,20 +1645,25 @@ static void single_state_process_party_b(struct cdr_object *cdr, struct ast_chan
 
 static int single_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer)
 {
-       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
-
        if (caller && !strcasecmp(cdr->party_a.snapshot->name, caller->name)) {
-               cdr_object_swap_snapshot(&cdr->party_a, caller);
-               CDR_DEBUG(mod_cfg, "%p - Updated Party A %s snapshot\n", cdr,
-                               cdr->party_a.snapshot->name);
+               base_process_party_a(cdr, caller);
+               CDR_DEBUG("%p - Updated Party A %s snapshot\n", cdr,
+                       cdr->party_a.snapshot->name);
                cdr_object_swap_snapshot(&cdr->party_b, peer);
-               CDR_DEBUG(mod_cfg, "%p - Updated Party B %s snapshot\n", cdr,
-                               cdr->party_b.snapshot->name);
+               cdr_all_relink(cdr);
+               CDR_DEBUG("%p - Updated Party B %s snapshot\n", cdr,
+                       cdr->party_b.snapshot->name);
+
+               /* If we have two parties, lock the application that caused the
+                * two parties to be associated. This prevents mid-call event
+                * macros/gosubs from perturbing the CDR application/data
+                */
+               ast_set_flag(&cdr->flags, AST_CDR_LOCK_APP);
        } else if (!strcasecmp(cdr->party_a.snapshot->name, peer->name)) {
                /* We're the entity being dialed, i.e., outbound origination */
-               cdr_object_swap_snapshot(&cdr->party_a, peer);
-               CDR_DEBUG(mod_cfg, "%p - Updated Party A %s snapshot\n", cdr,
-                               cdr->party_a.snapshot->name);
+               base_process_party_a(cdr, peer);
+               CDR_DEBUG("%p - Updated Party A %s snapshot\n", cdr,
+                       cdr->party_a.snapshot->name);
        }
 
        cdr_object_transition_state(cdr, &dial_state_fn_table);
@@ -1458,7 +1685,6 @@ static int single_state_process_dial_begin(struct cdr_object *cdr, struct ast_ch
 static int single_state_bridge_enter_comparison(struct cdr_object *cdr,
                struct cdr_object *cand_cdr)
 {
-       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
        struct cdr_object_snapshot *party_a;
 
        /* Don't match on ourselves */
@@ -1469,9 +1695,10 @@ static int single_state_bridge_enter_comparison(struct cdr_object *cdr,
        /* Try the candidate CDR's Party A first */
        party_a = cdr_object_pick_party_a(&cdr->party_a, &cand_cdr->party_a);
        if (!strcasecmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
-               CDR_DEBUG(mod_cfg, "%p - Party A %s has new Party B %s\n",
+               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
@@ -1489,9 +1716,10 @@ static int single_state_bridge_enter_comparison(struct cdr_object *cdr,
        }
        party_a = cdr_object_pick_party_a(&cdr->party_a, &cand_cdr->party_b);
        if (!strcasecmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
-               CDR_DEBUG(mod_cfg, "%p - Party A %s has new Party B %s\n",
+               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;
        }
 
@@ -1515,11 +1743,10 @@ static enum process_bridge_enter_results single_state_process_bridge_enter(struc
        for (it_cdrs = ao2_iterator_init(bridge->channels, 0);
                !success && (channel_id = ao2_iterator_next(&it_cdrs));
                ao2_ref(channel_id, -1)) {
-               RAII_VAR(struct cdr_object *, cand_cdr_master,
-                       ao2_find(active_cdrs_by_channel, channel_id, OBJ_KEY),
-                       ao2_cleanup);
+               struct cdr_object *cand_cdr_master;
                struct cdr_object *cand_cdr;
 
+               cand_cdr_master = ao2_find(active_cdrs_master, channel_id, OBJ_SEARCH_KEY);
                if (!cand_cdr_master) {
                        continue;
                }
@@ -1541,6 +1768,7 @@ static enum process_bridge_enter_results single_state_process_bridge_enter(struc
                        break;
                }
                ao2_unlock(cand_cdr_master);
+               ao2_cleanup(cand_cdr_master);
        }
        ao2_iterator_destroy(&it_cdrs);
 
@@ -1567,11 +1795,9 @@ static int single_state_process_parking_bridge_enter(struct cdr_object *cdr, str
 static void dial_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
 {
        ast_assert(snapshot != NULL);
+       ast_assert(cdr->party_b.snapshot
+               && !strcasecmp(cdr->party_b.snapshot->name, snapshot->name));
 
-       if (!cdr->party_b.snapshot
-               || strcasecmp(cdr->party_b.snapshot->name, snapshot->name)) {
-               return;
-       }
        cdr_object_swap_snapshot(&cdr->party_b, snapshot);
 
        /* If party B hangs up, finalize this CDR */
@@ -1594,9 +1820,6 @@ static int dial_state_process_dial_begin(struct cdr_object *cdr, struct ast_chan
  */
 static enum ast_cdr_disposition dial_status_to_disposition(const char *dial_status)
 {
-       RAII_VAR(struct module_config *, mod_cfg,
-               ao2_global_obj_ref(module_configs), ao2_cleanup);
-
        if (!strcmp(dial_status, "ANSWER")) {
                return AST_CDR_ANSWERED;
        } else if (!strcmp(dial_status, "BUSY")) {
@@ -1604,7 +1827,7 @@ static enum ast_cdr_disposition dial_status_to_disposition(const char *dial_stat
        } else if (!strcmp(dial_status, "CANCEL") || !strcmp(dial_status, "NOANSWER")) {
                return AST_CDR_NOANSWER;
        } else if (!strcmp(dial_status, "CONGESTION")) {
-               if (!ast_test_flag(&mod_cfg->general->settings, CDR_CONGESTION)) {
+               if (!is_cdr_flag_set(CDR_CONGESTION)) {
                        return AST_CDR_FAILED;
                } else {
                        return AST_CDR_CONGESTION;
@@ -1617,8 +1840,6 @@ static enum ast_cdr_disposition dial_status_to_disposition(const char *dial_stat
 
 static int dial_state_process_dial_end(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer, const char *dial_status)
 {
-       RAII_VAR(struct module_config *, mod_cfg,
-                       ao2_global_obj_ref(module_configs), ao2_cleanup);
        struct ast_channel_snapshot *party_a;
 
        if (caller) {
@@ -1651,8 +1872,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);
@@ -1664,50 +1883,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)) {
-               RAII_VAR(struct cdr_object *, cand_cdr_master,
-                       ao2_find(active_cdrs_by_channel, channel_id, OBJ_KEY),
-                       ao2_cleanup);
-               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;
 
-               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_master, 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_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);
@@ -1769,10 +1989,9 @@ static int dialed_pending_state_process_dial_begin(struct cdr_object *cdr, struc
 
 static void bridge_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
 {
-       if (!cdr->party_b.snapshot
-               || strcasecmp(cdr->party_b.snapshot->name, snapshot->name)) {
-               return;
-       }
+       ast_assert(cdr->party_b.snapshot
+               && !strcasecmp(cdr->party_b.snapshot->name, snapshot->name));
+
        cdr_object_swap_snapshot(&cdr->party_b, snapshot);
 
        /* If party B hangs up, finalize this CDR */
@@ -1817,12 +2036,8 @@ static void finalized_state_init_function(struct cdr_object *cdr)
 
 static int finalized_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
 {
-       RAII_VAR(struct module_config *, mod_cfg,
-               ao2_global_obj_ref(module_configs), ao2_cleanup);
-
-       /* If we ignore hangup logic, indicate that we don't need a new CDR */
-       if (ast_test_flag(&mod_cfg->general->settings, CDR_END_BEFORE_H_EXTEN)
-               && ast_test_flag(&snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)) {
+       if (ast_test_flag(&snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)
+               && is_cdr_flag_set(CDR_END_BEFORE_H_EXTEN)) {
                return 0;
        }
 
@@ -1830,6 +2045,42 @@ static int finalized_state_process_party_a(struct cdr_object *cdr, struct ast_ch
        return 1;
 }
 
+/*!
+ * \internal
+ * \brief Filter channel snapshots by technology
+ */
+static int filter_channel_snapshot(struct ast_channel_snapshot *snapshot)
+{
+       return snapshot->tech_properties & AST_CHAN_TP_INTERNAL;
+}
+
+/*!
+ * \internal
+ * \brief Filter a channel cache update
+ */
+static int filter_channel_cache_message(struct ast_channel_snapshot *old_snapshot,
+               struct ast_channel_snapshot *new_snapshot)
+{
+       int ret = 0;
+
+       /* Drop cache updates from certain channel technologies */
+       if (old_snapshot) {
+               ret |= filter_channel_snapshot(old_snapshot);
+       }
+       if (new_snapshot) {
+               ret |= filter_channel_snapshot(new_snapshot);
+       }
+
+       return ret;
+}
+
+static int dial_status_end(const char *dialstatus)
+{
+       return (strcmp(dialstatus, "RINGING") &&
+                       strcmp(dialstatus, "PROCEEDING") &&
+                       strcmp(dialstatus, "PROGRESS"));
+}
+
 /* TOPIC ROUTER CALLBACKS */
 
 /*!
@@ -1841,8 +2092,7 @@ static int finalized_state_process_party_a(struct cdr_object *cdr, struct ast_ch
  */
 static void handle_dial_message(void *data, struct stasis_subscription *sub, struct stasis_message *message)
 {
-       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
-       RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup);
+       struct cdr_object *cdr;
        struct ast_multi_channel_blob *payload = stasis_message_data(message);
        struct ast_channel_snapshot *caller;
        struct ast_channel_snapshot *peer;
@@ -1856,27 +2106,36 @@ static void handle_dial_message(void *data, struct stasis_subscription *sub, str
        if (!peer && !caller) {
                return;
        }
+
+       if (peer && filter_channel_snapshot(peer)) {
+               return;
+       }
+
+       if (caller && filter_channel_snapshot(caller)) {
+               return;
+       }
+
        dial_status_blob = ast_json_object_get(ast_multi_channel_blob_get_json(payload), "dialstatus");
        if (dial_status_blob) {
                dial_status = ast_json_string_get(dial_status_blob);
        }
 
-       CDR_DEBUG(mod_cfg, "Dial %s message for %s, %s: %u.%08u\n",
-                       ast_strlen_zero(dial_status) ? "Begin" : "End",
-                       caller ? caller->name : "(none)",
-                       peer ? peer->name : "(none)",
-                       (unsigned int)stasis_message_timestamp(message)->tv_sec,
-                       (unsigned int)stasis_message_timestamp(message)->tv_usec);
+       CDR_DEBUG("Dial %s message for %s, %s: %u.%08u\n",
+               ast_strlen_zero(dial_status) ? "Begin" : "End",
+               caller ? caller->name : "(none)",
+               peer ? peer->name : "(none)",
+               (unsigned int)stasis_message_timestamp(message)->tv_sec,
+               (unsigned int)stasis_message_timestamp(message)->tv_usec);
 
        /* Figure out who is running this show */
        if (caller) {
-               cdr = ao2_find(active_cdrs_by_channel, caller->uniqueid, OBJ_KEY);
+               cdr = ao2_find(active_cdrs_master, caller->uniqueid, OBJ_SEARCH_KEY);
        } else {
-               cdr = ao2_find(active_cdrs_by_channel, peer->uniqueid, OBJ_KEY);
+               cdr = ao2_find(active_cdrs_master, peer->uniqueid, OBJ_SEARCH_KEY);
        }
-
        if (!cdr) {
                ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", caller ? caller->name : peer->name);
+               ast_assert(0);
                return;
        }
 
@@ -1886,21 +2145,21 @@ static void handle_dial_message(void *data, struct stasis_subscription *sub, str
                        if (!it_cdr->fn_table->process_dial_begin) {
                                continue;
                        }
-                       CDR_DEBUG(mod_cfg, "%p - Processing Dial Begin message for channel %s, peer %s\n",
-                                       cdr,
-                                       caller ? caller->name : "(none)",
-                                       peer ? peer->name : "(none)");
+                       CDR_DEBUG("%p - Processing Dial Begin message for channel %s, peer %s\n",
+                               it_cdr,
+                               caller ? caller->name : "(none)",
+                               peer ? peer->name : "(none)");
                        res &= it_cdr->fn_table->process_dial_begin(it_cdr,
                                        caller,
                                        peer);
-               } else {
+               } else if (dial_status_end(dial_status)) {
                        if (!it_cdr->fn_table->process_dial_end) {
                                continue;
                        }
-                       CDR_DEBUG(mod_cfg, "%p - Processing Dial End message for channel %s, peer %s\n",
-                                       cdr,
-                                       caller ? caller->name : "(none)",
-                                       peer ? peer->name : "(none)");
+                       CDR_DEBUG("%p - Processing Dial End message for channel %s, peer %s\n",
+                               it_cdr,
+                               caller ? caller->name : "(none)",
+                               peer ? peer->name : "(none)");
                        it_cdr->fn_table->process_dial_end(it_cdr,
                                        caller,
                                        peer,
@@ -1913,91 +2172,74 @@ static void handle_dial_message(void *data, struct stasis_subscription *sub, str
                struct cdr_object *new_cdr;
 
                new_cdr = cdr_object_create_and_append(cdr);
-               if (!new_cdr) {
-                       return;
+               if (new_cdr) {
+                       new_cdr->fn_table->process_dial_begin(new_cdr, caller, peer);
                }
-               new_cdr->fn_table->process_dial_begin(new_cdr,
-                               caller,
-                               peer);
        }
        ao2_unlock(cdr);
+       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);
-               }
-       }
-       return 0;
-}
 
-static int cdr_object_update_party_b(void *obj, void *arg, 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 (!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;
 }
 
-/*!
- * \internal
- * \brief Filter channel snapshots by technology
- */
-static int filter_channel_snapshot(struct ast_channel_snapshot *snapshot)
+static int cdr_object_update_party_b(void *obj, void *arg, void *data, int flags)
 {
-       return snapshot->tech_properties & AST_CHAN_TP_INTERNAL;
-}
+       struct cdr_object *cdr = obj;
 
-/*!
- * \internal
- * \brief Filter a channel cache update
- */
-static int filter_channel_cache_message(struct ast_channel_snapshot *old_snapshot,
-               struct ast_channel_snapshot *new_snapshot)
-{
-       int ret = 0;
+       if (cdr->fn_table->process_party_b
+               && !strcasecmp(cdr->party_b_name, arg)) {
+               struct ast_channel_snapshot *party_b = data;
 
-       /* Drop cache updates from certain channel technologies */
-       if (old_snapshot) {
-               ret |= filter_channel_snapshot(old_snapshot);
-       }
-       if (new_snapshot) {
-               ret |= filter_channel_snapshot(new_snapshot);
-       }
+               /*
+                * 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;
+               }
 
-       return ret;
+               cdr->fn_table->process_party_b(cdr, party_b);
+       }
+       return 0;
 }
 
 /*! \brief Determine if we need to add a new CDR based on snapshots */
 static int check_new_cdr_needed(struct ast_channel_snapshot *old_snapshot,
                struct ast_channel_snapshot *new_snapshot)
 {
-       RAII_VAR(struct module_config *, mod_cfg,
-                       ao2_global_obj_ref(module_configs), ao2_cleanup);
-
-       if (!new_snapshot) {
-               return 0;
-       }
-
-       if (ast_test_flag(&new_snapshot->flags, AST_FLAG_DEAD)) {
+       /* If we're dead, we don't need a new CDR */
+       if (!new_snapshot
+               || (ast_test_flag(&new_snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)
+                       && is_cdr_flag_set(CDR_END_BEFORE_H_EXTEN))) {
                return 0;
        }
 
@@ -2022,13 +2264,10 @@ static int check_new_cdr_needed(struct ast_channel_snapshot *old_snapshot,
  */
 static void handle_channel_cache_message(void *data, struct stasis_subscription *sub, struct stasis_message *message)
 {
-       RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup);
-       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+       struct cdr_object *cdr;
        struct stasis_cache_update *update = stasis_message_data(message);
        struct ast_channel_snapshot *old_snapshot;
        struct ast_channel_snapshot *new_snapshot;
-       const char *uniqueid;
-       const char *name;
        struct cdr_object *it_cdr;
 
        ast_assert(update != NULL);
@@ -2036,8 +2275,6 @@ static void handle_channel_cache_message(void *data, struct stasis_subscription
 
        old_snapshot = stasis_message_data(update->old_snapshot);
        new_snapshot = stasis_message_data(update->new_snapshot);
-       uniqueid = new_snapshot ? new_snapshot->uniqueid : old_snapshot->uniqueid;
-       name = new_snapshot ? new_snapshot->name : old_snapshot->name;
 
        if (filter_channel_cache_message(old_snapshot, new_snapshot)) {
                return;
@@ -2048,53 +2285,65 @@ static void handle_channel_cache_message(void *data, struct stasis_subscription
                if (!cdr) {
                        return;
                }
-               ao2_link(active_cdrs_by_channel, cdr);
+               cdr->is_root = 1;
+               ao2_link(active_cdrs_master, cdr);
+       } else {
+               const char *uniqueid;
+
+               uniqueid = new_snapshot ? new_snapshot->uniqueid : old_snapshot->uniqueid;
+               cdr = ao2_find(active_cdrs_master, uniqueid, OBJ_SEARCH_KEY);
        }
 
        /* Handle Party A */
        if (!cdr) {
-               cdr = ao2_find(active_cdrs_by_channel, uniqueid, OBJ_KEY);
-       }
-       if (!cdr) {
+               const char *name;
+
+               name = new_snapshot ? new_snapshot->name : old_snapshot->name;
                ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", name);
-       } else {
+               ast_assert(0);
+       } else if (new_snapshot) {
+               int all_reject = 1;
+
                ao2_lock(cdr);
-               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);
-                       }
-                       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);
-                               }
+               for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+                       if (!it_cdr->fn_table->process_party_a) {
+                               continue;
                        }
-               } else {
-                       CDR_DEBUG(mod_cfg, "%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);
+                       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);
                        }
-                       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_master, 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);
 }
 
 struct bridge_leave_data {
@@ -2103,29 +2352,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;
 }
@@ -2155,11 +2400,7 @@ static void handle_bridge_leave_message(void *data, struct stasis_subscription *
        struct ast_bridge_blob *update = stasis_message_data(message);
        struct ast_bridge_snapshot *bridge = update->bridge;
        struct ast_channel_snapshot *channel = update->channel;
-       RAII_VAR(struct module_config *, mod_cfg,
-                       ao2_global_obj_ref(module_configs), ao2_cleanup);
-       RAII_VAR(struct cdr_object *, cdr,
-                       ao2_find(active_cdrs_by_channel, channel->uniqueid, OBJ_KEY),
-                       ao2_cleanup);
+       struct cdr_object *cdr;
        struct cdr_object *it_cdr;
        struct bridge_leave_data leave_data = {
                .bridge = bridge,
@@ -2171,13 +2412,19 @@ static void handle_bridge_leave_message(void *data, struct stasis_subscription *
                return;
        }
 
-       CDR_DEBUG(mod_cfg, "Bridge Leave message for %s: %u.%08u\n",
-                       channel->name,
-                       (unsigned int)stasis_message_timestamp(message)->tv_sec,
-                       (unsigned int)stasis_message_timestamp(message)->tv_usec);
+       if (filter_channel_snapshot(channel)) {
+               return;
+       }
+
+       CDR_DEBUG("Bridge Leave message for %s: %u.%08u\n",
+               channel->name,
+               (unsigned int)stasis_message_timestamp(message)->tv_sec,
+               (unsigned int)stasis_message_timestamp(message)->tv_usec);
 
+       cdr = ao2_find(active_cdrs_master, channel->uniqueid, OBJ_SEARCH_KEY);
        if (!cdr) {
                ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", channel->name);
+               ast_assert(0);
                return;
        }
 
@@ -2187,25 +2434,24 @@ static void handle_bridge_leave_message(void *data, struct stasis_subscription *
                if (!it_cdr->fn_table->process_bridge_leave) {
                        continue;
                }
-               CDR_DEBUG(mod_cfg, "%p - Processing Bridge Leave for %s\n",
-                               it_cdr, channel->name);
+               CDR_DEBUG("%p - Processing Bridge Leave for %s\n",
+                       it_cdr, channel->name);
                if (!it_cdr->fn_table->process_bridge_leave(it_cdr, bridge, channel)) {
                        ast_string_field_set(it_cdr, bridge, "");
                        left_bridge = 1;
                }
        }
-       if (!left_bridge) {
-               ao2_unlock(cdr);
-               return;
-       }
        ao2_unlock(cdr);
 
-       if (strcmp(bridge->subclass, "parking")) {
-               /* Party B */
-               ao2_callback(active_cdrs_by_channel, OBJ_NODATA,
-                               cdr_object_party_b_left_bridge_cb,
-                               &leave_data);
+       /* Party B */
+       if (left_bridge
+               && strcmp(bridge->subclass, "parking")) {
+               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);
        }
+
+       ao2_cleanup(cdr);
 }
 
 /*!
@@ -2217,8 +2463,6 @@ static void handle_bridge_leave_message(void *data, struct stasis_subscription *
 static void bridge_candidate_add_to_cdr(struct cdr_object *cdr,
                struct cdr_object_snapshot *party_b)
 {
-       RAII_VAR(struct module_config *,  mod_cfg,
-               ao2_global_obj_ref(module_configs), ao2_cleanup);
        struct cdr_object *new_cdr;
 
        new_cdr = cdr_object_create_and_append(cdr);
@@ -2226,10 +2470,11 @@ 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);
-       CDR_DEBUG(mod_cfg, "%p - Party A %s has new Party B %s\n",
+       CDR_DEBUG("%p - Party A %s has new Party B %s\n",
                new_cdr, new_cdr->party_a.snapshot->name,
                party_b->snapshot->name);
 }
@@ -2246,14 +2491,12 @@ static void bridge_candidate_add_to_cdr(struct cdr_object *cdr,
  * \param cand_cdr The \ref cdr_object that is a candidate
  *
  */
-static int bridge_candidate_process(struct cdr_object *cdr, struct cdr_object *base_cand_cdr)
+static void bridge_candidate_process(struct cdr_object *cdr, struct cdr_object *base_cand_cdr)
 {
-       RAII_VAR(struct module_config *, mod_cfg,
-               ao2_global_obj_ref(module_configs), ao2_cleanup);
        struct cdr_object_snapshot *party_a;
        struct cdr_object *cand_cdr;
 
-       SCOPED_AO2LOCK(lock, base_cand_cdr);
+       ao2_lock(base_cand_cdr);
 
        for (cand_cdr = base_cand_cdr; cand_cdr; cand_cdr = cand_cdr->next) {
                /* Skip any records that are not in this bridge */
@@ -2265,7 +2508,7 @@ static int bridge_candidate_process(struct cdr_object *cdr, struct cdr_object *b
                if (!strcasecmp(cdr->party_a.snapshot->name, cand_cdr->party_a.snapshot->name)
                        || (cdr->party_b.snapshot
                                && !strcasecmp(cdr->party_b.snapshot->name, cand_cdr->party_a.snapshot->name))) {
-                       return 0;
+                       break;
                }
 
                party_a = cdr_object_pick_party_a(&cdr->party_a, &cand_cdr->party_a);
@@ -2273,7 +2516,7 @@ static int bridge_candidate_process(struct cdr_object *cdr, struct cdr_object *b
                 * Party B */
                if (!strcasecmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
                        bridge_candidate_add_to_cdr(cdr, &cand_cdr->party_a);
-                       return 0;
+                       break;
                }
 
                /* We're Party B. Check if we can add ourselves immediately or if we need
@@ -2282,18 +2525,22 @@ static int bridge_candidate_process(struct cdr_object *cdr, struct cdr_object *b
                        && strcasecmp(cand_cdr->party_b.snapshot->name, cdr->party_a.snapshot->name)) {
                        bridge_candidate_add_to_cdr(cand_cdr, &cdr->party_a);
                } else {
-                       CDR_DEBUG(mod_cfg, "%p - Party A %s has new Party B %s\n",
+                       CDR_DEBUG("%p - Party A %s has new Party B %s\n",
                                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.
                         */
                        memset(&cand_cdr->end, 0, sizeof(cand_cdr->end));
                }
+
+               break;
        }
-       return 0;
+
+       ao2_unlock(base_cand_cdr);
 }
 
 /*!
@@ -2309,17 +2556,14 @@ static void handle_bridge_pairings(struct cdr_object *cdr, struct ast_bridge_sna
 
        it_channels = ao2_iterator_init(bridge->channels, 0);
        while ((channel_id = ao2_iterator_next(&it_channels))) {
-               RAII_VAR(struct cdr_object *, cand_cdr,
-                       ao2_find(active_cdrs_by_channel, channel_id, OBJ_KEY),
-                       ao2_cleanup);
+               struct cdr_object *cand_cdr;
 
-               if (!cand_cdr) {
-                       ao2_ref(channel_id, -1);
-                       continue;
+               cand_cdr = ao2_find(active_cdrs_master, channel_id, OBJ_SEARCH_KEY);
+               if (cand_cdr) {
+                       bridge_candidate_process(cdr, cand_cdr);
+                       ao2_ref(cand_cdr, -1);
                }
 
-               bridge_candidate_process(cdr, cand_cdr);
-
                ao2_ref(channel_id, -1);
        }
        ao2_iterator_destroy(&it_channels);
@@ -2334,8 +2578,6 @@ static void handle_parking_bridge_enter_message(struct cdr_object *cdr,
                struct ast_bridge_snapshot *bridge,
                struct ast_channel_snapshot *channel)
 {
-       RAII_VAR(struct module_config *, mod_cfg,
-                       ao2_global_obj_ref(module_configs), ao2_cleanup);
        int res = 1;
        struct cdr_object *it_cdr;
        struct cdr_object *new_cdr;
@@ -2347,8 +2589,8 @@ static void handle_parking_bridge_enter_message(struct cdr_object *cdr,
                        res &= it_cdr->fn_table->process_parking_bridge_enter(it_cdr, bridge, channel);
                }
                if (it_cdr->fn_table->process_party_a) {
-                       CDR_DEBUG(mod_cfg, "%p - Updating Party A %s snapshot\n", it_cdr,
-                                       channel->name);
+                       CDR_DEBUG("%p - Updating Party A %s snapshot\n", it_cdr,
+                               channel->name);
                        it_cdr->fn_table->process_party_a(it_cdr, channel);
                }
        }
@@ -2374,8 +2616,6 @@ static void handle_standard_bridge_enter_message(struct cdr_object *cdr,
                struct ast_bridge_snapshot *bridge,
                struct ast_channel_snapshot *channel)
 {
-       RAII_VAR(struct module_config *, mod_cfg,
-                       ao2_global_obj_ref(module_configs), ao2_cleanup);
        enum process_bridge_enter_results result;
        struct cdr_object *it_cdr;
        struct cdr_object *new_cdr;
@@ -2383,17 +2623,18 @@ static void handle_standard_bridge_enter_message(struct cdr_object *cdr,
 
        ao2_lock(cdr);
 
+try_again:
        for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
                if (it_cdr->fn_table->process_party_a) {
-                       CDR_DEBUG(mod_cfg, "%p - Updating Party A %s snapshot\n", it_cdr,
-                                       channel->name);
+                       CDR_DEBUG("%p - Updating Party A %s snapshot\n", it_cdr,
+                               channel->name);
                        it_cdr->fn_table->process_party_a(it_cdr, channel);
                }
 
                /* Notify all states that they have entered a bridge */
                if (it_cdr->fn_table->process_bridge_enter) {
-                       CDR_DEBUG(mod_cfg, "%p - Processing bridge enter for %s\n", it_cdr,
-                                       channel->name);
+                       CDR_DEBUG("%p - Processing bridge enter for %s\n", it_cdr,
+                               channel->name);
                        result = it_cdr->fn_table->process_bridge_enter(it_cdr, bridge, channel);
                        switch (result) {
                        case BRIDGE_ENTER_ONLY_PARTY:
@@ -2402,10 +2643,10 @@ static void handle_standard_bridge_enter_message(struct cdr_object *cdr,
                                if (!handled_cdr) {
                                        handled_cdr = it_cdr;
                                }
-                       break;
+                               break;
                        case BRIDGE_ENTER_NEED_CDR:
                                /* Pass */
-                       break;
+                               break;
                        case BRIDGE_ENTER_NO_PARTY_B:
                                /* We didn't win on any - end this CDR. If someone else comes in later
                                 * that is Party B to this CDR, it can re-activate this CDR.
@@ -2414,7 +2655,7 @@ static void handle_standard_bridge_enter_message(struct cdr_object *cdr,
                                        handled_cdr = it_cdr;
                                }
                                cdr_object_finalize(cdr);
-                       break;
+                               break;
                        }
                }
        }
@@ -2435,7 +2676,7 @@ static void handle_standard_bridge_enter_message(struct cdr_object *cdr,
                        /* This is guaranteed to succeed: the new CDR is created in the single state
                         * and will be able to handle the bridge enter message
                         */
-                       handle_standard_bridge_enter_message(cdr, bridge, channel);
+                       goto try_again;
                }
        }
        ao2_unlock(cdr);
@@ -2455,11 +2696,7 @@ static void handle_bridge_enter_message(void *data, struct stasis_subscription *
        struct ast_bridge_blob *update = stasis_message_data(message);
        struct ast_bridge_snapshot *bridge = update->bridge;
        struct ast_channel_snapshot *channel = update->channel;
-       RAII_VAR(struct cdr_object *, cdr,
-                       ao2_find(active_cdrs_by_channel, channel->uniqueid, OBJ_KEY),
-                       ao2_cleanup);
-       RAII_VAR(struct module_config *, mod_cfg,
-                       ao2_global_obj_ref(module_configs), ao2_cleanup);
+       struct cdr_object *cdr;
 
        if (filter_bridge_messages(bridge)) {
                return;
@@ -2469,13 +2706,15 @@ static void handle_bridge_enter_message(void *data, struct stasis_subscription *
                return;
        }
 
-       CDR_DEBUG(mod_cfg, "Bridge Enter message for channel %s: %u.%08u\n",
-                       channel->name,
-                       (unsigned int)stasis_message_timestamp(message)->tv_sec,
-                       (unsigned int)stasis_message_timestamp(message)->tv_usec);
+       CDR_DEBUG("Bridge Enter message for channel %s: %u.%08u\n",
+               channel->name,
+               (unsigned int)stasis_message_timestamp(message)->tv_sec,
+               (unsigned int)stasis_message_timestamp(message)->tv_usec);
 
+       cdr = ao2_find(active_cdrs_master, channel->uniqueid, OBJ_SEARCH_KEY);
        if (!cdr) {
                ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", channel->name);
+               ast_assert(0);
                return;
        }
 
@@ -2484,6 +2723,7 @@ static void handle_bridge_enter_message(void *data, struct stasis_subscription *
        } else {
                handle_standard_bridge_enter_message(cdr, bridge, channel);
        }
+       ao2_cleanup(cdr);
 }
 
 /*!
@@ -2498,9 +2738,7 @@ static void handle_parked_call_message(void *data, struct stasis_subscription *s
 {
        struct ast_parked_call_payload *payload = stasis_message_data(message);
        struct ast_channel_snapshot *channel = payload->parkee;
-       RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup);
-       RAII_VAR(struct module_config *, mod_cfg,
-                       ao2_global_obj_ref(module_configs), ao2_cleanup);
+       struct cdr_object *cdr;
        int unhandled = 1;
        struct cdr_object *it_cdr;
 
@@ -2514,14 +2752,19 @@ static void handle_parked_call_message(void *data, struct stasis_subscription *s
                return;
        }
 
-       CDR_DEBUG(mod_cfg, "Parked Call message for channel %s: %u.%08u\n",
-                       channel->name,
-                       (unsigned int)stasis_message_timestamp(message)->tv_sec,
-                       (unsigned int)stasis_message_timestamp(message)->tv_usec);
+       if (filter_channel_snapshot(channel)) {
+               return;
+       }
 
-       cdr = ao2_find(active_cdrs_by_channel, channel->uniqueid, OBJ_KEY);
+       CDR_DEBUG("Parked Call message for channel %s: %u.%08u\n",
+               channel->name,
+               (unsigned int)stasis_message_timestamp(message)->tv_sec,
+               (unsigned int)stasis_message_timestamp(message)->tv_usec);
+
+       cdr = ao2_find(active_cdrs_master, channel->uniqueid, OBJ_SEARCH_KEY);
        if (!cdr) {
                ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", channel->name);
+               ast_assert(0);
                return;
        }
 
@@ -2535,7 +2778,9 @@ static void handle_parked_call_message(void *data, struct stasis_subscription *s
 
        if (unhandled) {
                /* Nothing handled the messgae - we need a new one! */
-               struct cdr_object *new_cdr = cdr_object_create_and_append(cdr);
+               struct cdr_object *new_cdr;
+
+               new_cdr = cdr_object_create_and_append(cdr);
                if (new_cdr) {
                        /* As the new CDR is created in the single state, it is guaranteed
                         * to have a function for the parked call message and will handle
@@ -2546,81 +2791,192 @@ static void handle_parked_call_message(void *data, struct stasis_subscription *s
 
        ao2_unlock(cdr);
 
+       ao2_cleanup(cdr);
+}
+
+/*!
+ * \brief Handler for a synchronization message
+ * \param data Passed on
+ * \param sub The stasis subscription for this message callback
+ * \param topic The topic this message was published for
+ * \param message A blank ao2 object
+ * */
+static void handle_cdr_sync_message(void *data, struct stasis_subscription *sub,
+               struct stasis_message *message)
+{
+       return;
 }
 
 struct ast_cdr_config *ast_cdr_get_config(void)
 {
-       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
-       ao2_ref(mod_cfg->general, +1);
-       return mod_cfg->general;
+       struct ast_cdr_config *general;
+       struct module_config *mod_cfg;
+
+       mod_cfg = ao2_global_obj_ref(module_configs);
+       if (!mod_cfg) {
+               return NULL;
+       }
+       general = ao2_bump(mod_cfg->general);
+       ao2_cleanup(mod_cfg);
+       return general;
 }
 
 void ast_cdr_set_config(struct ast_cdr_config *config)
 {
-       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
-       ao2_cleanup(mod_cfg->general);
-       mod_cfg->general = config;
-       ao2_ref(mod_cfg->general, +1);
+       struct module_config *mod_cfg;
+
+       if (!config) {
+               return;
+       }
+
+       mod_cfg = ao2_global_obj_ref(module_configs);
+       if (!mod_cfg) {
+               return;
+       }
+
+       ao2_replace(mod_cfg->general, config);
+
+       cdr_set_debug_mode(mod_cfg);
+       cdr_toggle_runtime_options();
+
+       ao2_cleanup(mod_cfg);
 }
 
 int ast_cdr_is_enabled(void)
 {
-       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
-       return ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED);
+       return is_cdr_flag_set(CDR_ENABLED);
 }
 
-int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
+int ast_cdr_backend_suspend(const char *name)
 {
+       int success = -1;
        struct cdr_beitem *i = NULL;
 
-       if (!name)
-               return -1;
-
-       if (!be) {
-               ast_log(LOG_WARNING, "CDR engine '%s' lacks backend\n", name);
-               return -1;
+       AST_RWLIST_WRLOCK(&be_list);
+       AST_RWLIST_TRAVERSE(&be_list, i, list) {
+               if (!strcasecmp(name, i->name)) {
+                       ast_debug(3, "Suspending CDR backend %s\n", i->name);
+                       i->suspended = 1;
+                       success = 0;
+               }
        }
+       AST_RWLIST_UNLOCK(&be_list);
+
+       return success;
+}
+
+int ast_cdr_backend_unsuspend(const char *name)
+{
+       int success = -1;
+       struct cdr_beitem *i = NULL;
 
        AST_RWLIST_WRLOCK(&be_list);
        AST_RWLIST_TRAVERSE(&be_list, i, list) {
                if (!strcasecmp(name, i->name)) {
-                       ast_log(LOG_WARNING, "Already have a CDR backend called '%s'\n", name);
-                       AST_RWLIST_UNLOCK(&be_list);
-                       return -1;
+                       ast_debug(3, "Unsuspending CDR backend %s\n", i->name);
+                       i->suspended = 0;
+                       success = 0;
                }
        }
+       AST_RWLIST_UNLOCK(&be_list);
+
+       return success;
+}
+
+static int cdr_generic_register(struct be_list *generic_list, const char *name, const char *desc, ast_cdrbe be)
+{
+       struct cdr_beitem *i;
+       struct cdr_beitem *cur;
 
-       if (!(i = ast_calloc(1, sizeof(*i))))
+       if (!name) {
                return -1;
+       }
+
+       if (!be) {
+               ast_log(LOG_WARNING, "CDR engine '%s' lacks backend\n", name);
+
+               return -1;
+       }
+
+       i = ast_calloc(1, sizeof(*i));
+       if (!i) {
+               return -1;
+       }
 
        i->be = be;
        ast_copy_string(i->name, name, sizeof(i->name));
        ast_copy_string(i->desc, desc, sizeof(i->desc));
 
-       AST_RWLIST_INSERT_HEAD(&be_list, i, list);
-       AST_RWLIST_UNLOCK(&be_list);
+       AST_RWLIST_WRLOCK(generic_list);
+       AST_RWLIST_TRAVERSE(generic_list, cur, list) {
+               if (!strcasecmp(name, cur->name)) {
+                       ast_log(LOG_WARNING, "Already have a CDR backend called '%s'\n", name);
+                       AST_RWLIST_UNLOCK(generic_list);
+                       ast_free(i);
+
+                       return -1;
+               }
+       }
+
+       AST_RWLIST_INSERT_HEAD(generic_list, i, list);
+       AST_RWLIST_UNLOCK(generic_list);
 
        return 0;
 }
 
-void ast_cdr_unregister(const char *name)
+int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
 {
-       struct cdr_beitem *i = NULL;
+       return cdr_generic_register(&be_list, name, desc, be);
+}
 
-       AST_RWLIST_WRLOCK(&be_list);
-       AST_RWLIST_TRAVERSE_SAFE_BEGIN(&be_list, i, list) {
-               if (!strcasecmp(name, i->name)) {
-                       AST_RWLIST_REMOVE_CURRENT(list);
+int ast_cdr_modifier_register(const char *name, const char *desc, ast_cdrbe be)
+{
+       return cdr_generic_register((struct be_list *)&mo_list, name, desc, be);
+}
+
+static int ast_cdr_generic_unregister(struct be_list *generic_list, const char *name)
+{
+       struct cdr_beitem *match = NULL;
+       int active_count;
+
+       AST_RWLIST_WRLOCK(generic_list);
+       AST_RWLIST_TRAVERSE(generic_list, match, list) {
+               if (!strcasecmp(name, match->name)) {
                        break;
                }
        }
-       AST_RWLIST_TRAVERSE_SAFE_END;
-       AST_RWLIST_UNLOCK(&be_list);
 
-       if (i) {
-               ast_verb(2, "Unregistered '%s' CDR backend\n", name);
-               ast_free(i);
+       if (!match) {
+               AST_RWLIST_UNLOCK(generic_list);
+               return 0;
+       }
+
+       active_count = ao2_container_count(active_cdrs_master);
+
+       if (!match->suspended && active_count != 0) {
+               AST_RWLIST_UNLOCK(generic_list);
+               ast_log(AST_LOG_WARNING, "Unable to unregister CDR backend %s; %d CDRs are still active\n",
+                       name, active_count);
+               return -1;
        }
+
+       AST_RWLIST_REMOVE(generic_list, match, list);
+       AST_RWLIST_UNLOCK(generic_list);
+
+       ast_verb(2, "Unregistered '%s' CDR backend\n", name);
+       ast_free(match);
+
+       return 0;
+}
+
+int ast_cdr_unregister(const char *name)
+{
+       return ast_cdr_generic_unregister(&be_list, name);
+}
+
+int ast_cdr_modifier_unregister(const char *name)
+{
+       return ast_cdr_generic_unregister((struct be_list *)&mo_list, name);
 }
 
 struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr)
@@ -2747,7 +3103,7 @@ void ast_cdr_format_var(struct ast_cdr *cdr, const char *name, char **ret, char
        }
 }
 
-/*
+/*!
  * \internal
  * \brief Callback that finds all CDRs that reference a particular channel by name
  */
@@ -2763,7 +3119,7 @@ static int cdr_object_select_all_by_name_cb(void *obj, void *arg, int flags)
        return 0;
 }
 
-/*
+/*!
  * \internal
  * \brief Callback that finds a CDR by channel name
  */
@@ -2818,7 +3174,7 @@ int ast_cdr_setvar(const char *channel_name, const char *name, const char *value
                }
        }
 
-       it_cdrs = ao2_callback(active_cdrs_by_channel, OBJ_MULTIPLE, cdr_object_select_all_by_name_cb, arg);
+       it_cdrs = ao2_callback(active_cdrs_master, OBJ_MULTIPLE, cdr_object_select_all_by_name_cb, arg);
        if (!it_cdrs) {
                ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
                return -1;
@@ -2829,7 +3185,7 @@ int ast_cdr_setvar(const char *channel_name, const char *name, const char *value
                for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
                        struct varshead *headp = NULL;
 
-                       if (it_cdr->fn_table == &finalized_state_fn_table) {
+                       if (it_cdr->fn_table == &finalized_state_fn_table && it_cdr->next != NULL) {
                                continue;
                        }
                        if (!strcasecmp(channel_name, it_cdr->party_a.snapshot->name)) {
@@ -2904,7 +3260,7 @@ static int cdr_object_format_property(struct cdr_object *cdr_obj, const char *na
        } else if (!strcasecmp(name, "billsec")) {
                snprintf(value, length, "%ld", cdr_object_get_billsec(cdr_obj));
        } else if (!strcasecmp(name, "disposition")) {
-               snprintf(value, length, "%d", cdr_obj->disposition);
+               snprintf(value, length, "%u", cdr_obj->disposition);
        } else if (!strcasecmp(name, "amaflags")) {
                snprintf(value, length, "%d", party_a->amaflags);
        } else if (!strcasecmp(name, "accountcode")) {
@@ -2922,7 +3278,7 @@ static int cdr_object_format_property(struct cdr_object *cdr_obj, const char *na
        } else if (!strcasecmp(name, "userfield")) {
                ast_copy_string(value, cdr_obj->party_a.userfield, length);
        } else if (!strcasecmp(name, "sequence")) {
-               snprintf(value, length, "%d", cdr_obj->sequence);
+               snprintf(value, length, "%u", cdr_obj->sequence);
        } else {
                return 1;
        }
@@ -2946,20 +3302,21 @@ static struct cdr_object *cdr_object_get_by_name(const char *name)
        }
 
        param = ast_strdupa(name);
-       return ao2_callback(active_cdrs_by_channel, 0, cdr_object_get_by_name_cb, param);
+       return ao2_callback(active_cdrs_master, 0, cdr_object_get_by_name_cb, param);
 }
 
 int ast_cdr_getvar(const char *channel_name, const char *name, char *value, size_t length)
 {
-       RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup);
+       struct cdr_object *cdr;
        struct cdr_object *cdr_obj;
 
-       if (!cdr) {
-               ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
+       if (ast_strlen_zero(name)) {
                return 1;
        }
 
-       if (ast_strlen_zero(name)) {
+       cdr = cdr_object_get_by_name(channel_name);
+       if (!cdr) {
+               ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
                return 1;
        }
 
@@ -2973,24 +3330,24 @@ int ast_cdr_getvar(const char *channel_name, const char *name, char *value, size
 
        ao2_unlock(cdr);
 
+       ao2_cleanup(cdr);
        return 0;
 }
 
 int ast_cdr_serialize_variables(const char *channel_name, struct ast_str **buf, char delim, char sep)
 {
-       RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup);
+       struct cdr_object *cdr;
        struct cdr_object *it_cdr;
        struct ast_var_t *variable;
        const char *var;
-       RAII_VAR(char *, workspace, ast_malloc(256), ast_free);
+       char workspace[256];
        int total = 0, x = 0, i;
 
-       if (!workspace) {
-               return 0;
-       }
-
+       cdr = cdr_object_get_by_name(channel_name);
        if (!cdr) {
-               ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
+               if (is_cdr_flag_set(CDR_ENABLED)) {
+                       ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
+               }
                return 0;
        }
 
@@ -2998,8 +3355,9 @@ int ast_cdr_serialize_variables(const char *channel_name, struct ast_str **buf,
 
        ao2_lock(cdr);
        for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
-               if (++x > 1)
+               if (++x > 1) {
                        ast_str_append(buf, 0, "\n");
+               }
 
                AST_LIST_TRAVERSE(&it_cdr->party_a.variables, variable, entries) {
                        if (!(var = ast_var_name(variable))) {
@@ -3030,6 +3388,7 @@ int ast_cdr_serialize_variables(const char *channel_name, struct ast_str **buf,
                }
        }
        ao2_unlock(cdr);
+       ao2_cleanup(cdr);
        return total;
 }
 
@@ -3077,81 +3436,105 @@ 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) {
-                       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));
+
+               ast_copy_string(cdr->party_b.userfield, info->userfield,
+                       sizeof(cdr->party_b.userfield));
        }
+
        return 0;
 }
 
 void ast_cdr_setuserfield(const char *channel_name, const char *userfield)
 {
-       RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup);
+       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;
 
        /* Handle Party A */
+       cdr = cdr_object_get_by_name(channel_name);
        if (cdr) {
                ao2_lock(cdr);
                for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
-                       if (it_cdr->fn_table == &finalized_state_fn_table) {
+                       if (it_cdr->fn_table == &finalized_state_fn_table && it_cdr->next != NULL) {
                                continue;
                        }
-                       strcpy(it_cdr->party_a.userfield, userfield);
+                       ast_copy_string(it_cdr->party_a.userfield, userfield,
+                               sizeof(it_cdr->party_a.userfield));
                }
                ao2_unlock(cdr);
        }
 
        /* 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);
 }
 
 static void post_cdr(struct ast_cdr *cdr)
 {
-       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+       struct module_config *mod_cfg;
        struct cdr_beitem *i;
 
+       mod_cfg = ao2_global_obj_ref(module_configs);
+       if (!mod_cfg) {
+               return;
+       }
+
        for (; cdr ; cdr = cdr->next) {
                /* For people, who don't want to see unanswered single-channel events */
                if (!ast_test_flag(&mod_cfg->general->settings, CDR_UNANSWERED) &&
                                cdr->disposition < AST_CDR_ANSWERED &&
                                (ast_strlen_zero(cdr->channel) || ast_strlen_zero(cdr->dstchannel))) {
-                       ast_debug(1, "Skipping CDR  for %s since we weren't answered\n", cdr->channel);
+                       ast_debug(1, "Skipping CDR for %s since we weren't answered\n", cdr->channel);
                        continue;
                }
 
+               /* Modify CDR's */
+               AST_RWLIST_RDLOCK(&mo_list);
+               AST_RWLIST_TRAVERSE(&mo_list, i, list) {
+                       i->be(cdr);
+               }
+               AST_RWLIST_UNLOCK(&mo_list);
+
                if (ast_test_flag(cdr, AST_CDR_FLAG_DISABLE)) {
                        continue;
                }
                AST_RWLIST_RDLOCK(&be_list);
                AST_RWLIST_TRAVERSE(&be_list, i, list) {
-                       i->be(cdr);
+                       if (!i->suspended) {
+                               i->be(cdr);
+                       }
                }
                AST_RWLIST_UNLOCK(&be_list);
        }
+       ao2_cleanup(mod_cfg);
 }
 
 int ast_cdr_set_property(const char *channel_name, enum ast_cdr_options option)
 {
-       RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup);
+       struct cdr_object *cdr;
        struct cdr_object *it_cdr;
 
+       cdr = cdr_object_get_by_name(channel_name);
        if (!cdr) {
                return -1;
        }
@@ -3169,14 +3552,16 @@ int ast_cdr_set_property(const char *channel_name, enum ast_cdr_options option)
        }
        ao2_unlock(cdr);
 
+       ao2_cleanup(cdr);
        return 0;
 }
 
 int ast_cdr_clear_property(const char *channel_name, enum ast_cdr_options option)
 {
-       RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup);
+       struct cdr_object *cdr;
        struct cdr_object *it_cdr;
 
+       cdr = cdr_object_get_by_name(channel_name);
        if (!cdr) {
                return -1;
        }
@@ -3190,15 +3575,17 @@ int ast_cdr_clear_property(const char *channel_name, enum ast_cdr_options option
        }
        ao2_unlock(cdr);
 
+       ao2_cleanup(cdr);
        return 0;
 }
 
-int ast_cdr_reset(const char *channel_name, struct ast_flags *options)
+int ast_cdr_reset(const char *channel_name, int keep_variables)
 {
-       RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup);
+       struct cdr_object *cdr;
        struct ast_var_t *vardata;
        struct cdr_object *it_cdr;
 
+       cdr = cdr_object_get_by_name(channel_name);
        if (!cdr) {
                return -1;
        }
@@ -3206,7 +3593,7 @@ int ast_cdr_reset(const char *channel_name, struct ast_flags *options)
        ao2_lock(cdr);
        for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
                /* clear variables */
-               if (!ast_test_flag(options, AST_CDR_FLAG_KEEP_VARS)) {
+               if (!keep_variables) {
                        while ((vardata = AST_LIST_REMOVE_HEAD(&it_cdr->party_a.variables, entries))) {
                                ast_var_delete(vardata);
                        }
@@ -3226,6 +3613,7 @@ int ast_cdr_reset(const char *channel_name, struct ast_flags *options)
        }
        ao2_unlock(cdr);
 
+       ao2_cleanup(cdr);
        return 0;
 }
 
@@ -3242,6 +3630,7 @@ int ast_cdr_fork(const char *channel_name, struct ast_flags *options)
 
        {
                SCOPED_AO2LOCK(lock, cdr);
+
                cdr_obj = cdr->last;
                if (cdr_obj->fn_table == &finalized_state_fn_table) {
                        /* If the last CDR in the chain is finalized, don't allow a fork -
@@ -3260,12 +3649,22 @@ int ast_cdr_fork(const char *channel_name, struct ast_flags *options)
                }
                new_cdr->fn_table = cdr_obj->fn_table;
                ast_string_field_set(new_cdr, bridge, cdr->bridge);
+               ast_string_field_set(new_cdr, appl, cdr->appl);
+               ast_string_field_set(new_cdr, data, cdr->data);
+               ast_string_field_set(new_cdr, context, cdr->context);
+               ast_string_field_set(new_cdr, exten, cdr->exten);
                new_cdr->flags = cdr->flags;
+               /* Explicitly clear the AST_CDR_LOCK_APP flag - we want
+                * the application to be changed on the new CDR if the
+                * dialplan demands it
+                */
+               ast_clear_flag(&new_cdr->flags, AST_CDR_LOCK_APP);
 
                /* If there's a Party B, copy it over as well */
                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)) {
@@ -3347,7 +3746,7 @@ static void *do_batch_backend_process(void *data)
 
 static void cdr_submit_batch(int do_shutdown)
 {
-       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+       struct module_config *mod_cfg;
        struct cdr_batch_item *oldbatchitems = NULL;
        pthread_t batch_post_thread = AST_PTHREADT_NULL;
 
@@ -3362,9 +3761,13 @@ static void cdr_submit_batch(int do_shutdown)
        reset_batch();
        ast_mutex_unlock(&cdr_batch_lock);
 
+       mod_cfg = ao2_global_obj_ref(module_configs);
+
        /* if configured, spawn a new thread to post these CDRs,
           also try to save as much as possible if we are shutting down safely */
-       if (ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SCHEDULER_ONLY) || do_shutdown) {
+       if (!mod_cfg
+               || ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SCHEDULER_ONLY)
+               || do_shutdown) {
                ast_debug(1, "CDR single-threaded batch processing begins now\n");
                do_batch_backend_process(oldbatchitems);
        } else {
@@ -3375,30 +3778,39 @@ static void cdr_submit_batch(int do_shutdown)
                        ast_debug(1, "CDR multi-threaded batch processing begins now\n");
                }
        }
+
+       ao2_cleanup(mod_cfg);
 }
 
 static int submit_scheduled_batch(const void *data)
 {
-       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+       struct module_config *mod_cfg;
+       int nextms;
+
        cdr_submit_batch(0);
-       /* manually reschedule from this point in time */
 
-       ast_mutex_lock(&cdr_sched_lock);
-       cdr_sched = ast_sched_add(sched, mod_cfg->general->batch_settings.size * 1000, submit_scheduled_batch, NULL);
-       ast_mutex_unlock(&cdr_sched_lock);
-       /* returning zero so the scheduler does not automatically reschedule */
-       return 0;
+       mod_cfg = ao2_global_obj_ref(module_configs);
+       if (!mod_cfg) {
+               return 0;
+       }
+
+       /* Calculate the next scheduled interval */
+       nextms = mod_cfg->general->batch_settings.time * 1000;
+
+       ao2_cleanup(mod_cfg);
+
+       return nextms;
 }
 
 /*! Do not hold the batch lock while calling this function */
-static void submit_unscheduled_batch(void)
+static void start_batch_mode(void)
 {
        /* Prevent two deletes from happening at the same time */
        ast_mutex_lock(&cdr_sched_lock);
        /* this is okay since we are not being called from within the scheduler */
        AST_SCHED_DEL(sched, cdr_sched);
        /* schedule the submission to occur ASAP (1 ms) */
-       cdr_sched = ast_sched_add(sched, 1, submit_scheduled_batch, NULL);
+       cdr_sched = ast_sched_add_variable(sched, 1, submit_scheduled_batch, NULL, 1);
        ast_mutex_unlock(&cdr_sched_lock);
 
        /* signal the do_cdr thread to wakeup early and do some work (that lazy thread ;) */
@@ -3419,7 +3831,7 @@ static void cdr_detach(struct ast_cdr *cdr)
        }
 
        /* maybe they disabled CDR stuff completely, so just drop it */
-       if (!ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
+       if (!mod_cfg || !ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
                ast_debug(1, "Dropping CDR !\n");
                ast_cdr_free(cdr);
                return;
@@ -3463,9 +3875,9 @@ static void cdr_detach(struct ast_cdr *cdr)
        }
        ast_mutex_unlock(&cdr_batch_lock);
 
-       /* Don't call submit_unscheduled_batch with the cdr_batch_lock held */
+       /* Don't submit a batch with cdr_batch_lock held */
        if (submit_batch) {
-               submit_unscheduled_batch();
+               start_batch_mode();
        }
 }
 
@@ -3479,7 +3891,7 @@ static void *do_cdr(void *data)
                struct timeval now;
                schedms = ast_sched_wait(sched);
                /* this shouldn't happen, but provide a 1 second default just in case */
-               if (schedms <= 0)
+               if (schedms < 0)
                        schedms = 1000;
                now = ast_tvadd(ast_tvnow(), ast_samp2tv(schedms, 1000));
                timeout.tv_sec = now.tv_sec;
@@ -3489,7 +3901,7 @@ static void *do_cdr(void *data)
                ast_cond_timedwait(&cdr_pending_cond, &cdr_pending_lock, &timeout);
                numevents = ast_sched_runq(sched);
                ast_mutex_unlock(&cdr_pending_lock);
-               ast_debug(2, "Processed %d scheduled CDR batches from the run queue\n", numevents);
+               ast_debug(2, "Processed %d CDR batches from the run queue\n", numevents);
        }
 
        return NULL;
@@ -3497,7 +3909,7 @@ static void *do_cdr(void *data)
 
 static char *handle_cli_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+       struct module_config *mod_cfg;
 
        switch (cmd) {
        case CLI_INIT:
@@ -3515,6 +3927,11 @@ static char *handle_cli_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_a
                return CLI_SHOWUSAGE;
        }
 
+       mod_cfg = ao2_global_obj_ref(module_configs);
+       if (!mod_cfg) {
+               ast_cli(a->fd, "Could not set CDR debugging mode\n");
+               return CLI_SUCCESS;
+       }
        if (!strcasecmp(a->argv[3], "on")
                && !ast_test_flag(&mod_cfg->general->settings, CDR_DEBUG)) {
                ast_set_flag(&mod_cfg->general->settings, CDR_DEBUG);
@@ -3524,6 +3941,8 @@ static char *handle_cli_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_a
                ast_clear_flag(&mod_cfg->general->settings, CDR_DEBUG);
                ast_cli(a->fd, "CDR debugging disabled\n");
        }
+       cdr_set_debug_mode(mod_cfg);
+       ao2_cleanup(mod_cfg);
 
        return CLI_SUCCESS;
 }
@@ -3531,18 +3950,14 @@ static char *handle_cli_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_a
 /*! \brief Complete user input for 'cdr show' */
 static char *cli_complete_show(struct ast_cli_args *a)
 {
-       char *result = NULL;
        int wordlen = strlen(a->word);
-       int which = 0;
        struct ao2_iterator it_cdrs;
        struct cdr_object *cdr;
 
-       it_cdrs = ao2_iterator_init(active_cdrs_by_channel, 0);
+       it_cdrs = ao2_iterator_init(active_cdrs_master, 0);
        while ((cdr = ao2_iterator_next(&it_cdrs))) {
-               if (!strncasecmp(a->word, cdr->party_a.snapshot->name, wordlen) &&
-                       (++which > a->n)) {
-                       result = ast_strdup(cdr->party_a.snapshot->name);
-                       if (result) {
+               if (!strncasecmp(a->word, cdr->party_a.snapshot->name, wordlen)) {
+                       if (ast_cli_completion_add(ast_strdup(cdr->party_a.snapshot->name))) {
                                ao2_ref(cdr, -1);
                                break;
                        }
@@ -3550,7 +3965,8 @@ static char *cli_complete_show(struct ast_cli_args *a)
                ao2_ref(cdr, -1);
        }
        ao2_iterator_destroy(&it_cdrs);
-       return result;
+
+       return NULL;
 }
 
 static void cli_show_channels(struct ast_cli_args *a)
@@ -3569,7 +3985,7 @@ static void cli_show_channels(struct ast_cli_args *a)
        ast_cli(a->fd, "--------------------------------------------------\n");
        ast_cli(a->fd, TITLE_STRING, "Channel", "Dst. Channel", "LastApp", "Start", "Answer", "End", "Billsec", "Duration");
 
-       it_cdrs = ao2_iterator_init(active_cdrs_by_channel, 0);
+       it_cdrs = ao2_iterator_init(active_cdrs_master, 0);
        for (; (cdr = ao2_iterator_next(&it_cdrs)); ao2_cleanup(cdr)) {
                struct cdr_object *it_cdr;
                struct timeval start_time = { 0, };
@@ -3625,12 +4041,12 @@ static void cli_show_channel(struct ast_cli_args *a)
        char answer_time_buffer[64];
        char end_time_buffer[64];
        const char *channel_name = a->argv[3];
-       RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup);
+       struct cdr_object *cdr;
 
 #define TITLE_STRING "%-10.10s %-20.20s %-25.25s %-15.15s %-15.15s %-8.8s %-8.8s %-8.8s %-8.8s %-8.8s\n"
 #define FORMAT_STRING "%-10.10s %-20.20s %-25.25s %-15.15s %-15.15s %-8.8s %-8.8s %-8.8s %-8.8ld %-8.8ld\n"
 
-       cdr = ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY);
+       cdr = cdr_object_get_by_name(channel_name);
        if (!cdr) {
                ast_cli(a->fd, "Unknown channel: %s\n", channel_name);
                return;
@@ -3644,6 +4060,7 @@ static void cli_show_channel(struct ast_cli_args *a)
        ao2_lock(cdr);
        for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
                struct timeval end;
+
                if (snapshot_is_dialed(it_cdr->party_a.snapshot)) {
                        continue;
                }
@@ -3669,6 +4086,9 @@ static void cli_show_channel(struct ast_cli_args *a)
                                (long)ast_tvdiff_ms(end, it_cdr->start) / 1000);
        }
        ao2_unlock(cdr);
+
+       ao2_cleanup(cdr);
+
 #undef FORMAT_STRING
 #undef TITLE_STRING
 }
@@ -3706,7 +4126,7 @@ static char *handle_cli_show(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
        struct cdr_beitem *beitem = NULL;
-       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+       struct module_config *mod_cfg;
        int cnt = 0;
        long nextbatchtime = 0;
 
@@ -3725,6 +4145,11 @@ static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_
                return CLI_SHOWUSAGE;
        }
 
+       mod_cfg = ao2_global_obj_ref(module_configs);
+       if (!mod_cfg) {
+               return CLI_FAILURE;
+       }
+
        ast_cli(a->fd, "\n");
        ast_cli(a->fd, "Call Detail Record (CDR) settings\n");
        ast_cli(a->fd, "----------------------------------\n");
@@ -3743,8 +4168,8 @@ static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_
                        ast_cli(a->fd, "  Safe shutdown:              %s\n", ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SAFE_SHUTDOWN) ? "Enabled" : "Disabled");
                        ast_cli(a->fd, "  Threading model:            %s\n", ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SCHEDULER_ONLY) ? "Scheduler only" : "Scheduler plus separate threads");
                        ast_cli(a->fd, "  Current batch size:         %d record%s\n", cnt, ESS(cnt));
-                       ast_cli(a->fd, "  Maximum batch size:         %d record%s\n", mod_cfg->general->batch_settings.size, ESS(mod_cfg->general->batch_settings.size));
-                       ast_cli(a->fd, "  Maximum batch time:         %d second%s\n", mod_cfg->general->batch_settings.time, ESS(mod_cfg->general->batch_settings.time));
+                       ast_cli(a->fd, "  Maximum batch size:         %u record%s\n", mod_cfg->general->batch_settings.size, ESS(mod_cfg->general->batch_settings.size));
+                       ast_cli(a->fd, "  Maximum batch time:         %u second%s\n", mod_cfg->general->batch_settings.time, ESS(mod_cfg->general->batch_settings.time));
                        ast_cli(a->fd, "  Next batch processing time: %ld second%s\n\n", nextbatchtime, ESS(nextbatchtime));
                }
                ast_cli(a->fd, "* Registered Backends\n");
@@ -3754,19 +4179,20 @@ static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_
                        ast_cli(a->fd, "    (none)\n");
                } else {
                        AST_RWLIST_TRAVERSE(&be_list, beitem, list) {
-                               ast_cli(a->fd, "    %s\n", beitem->name);
+                               ast_cli(a->fd, "    %s%s\n", beitem->name, beitem->suspended ? " (suspended) " : "");
                        }
                }
                AST_RWLIST_UNLOCK(&be_list);
                ast_cli(a->fd, "\n");
        }
 
+       ao2_cleanup(mod_cfg);
        return CLI_SUCCESS;
 }
 
 static char *handle_cli_submit(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+       struct module_config *mod_cfg;
 
        switch (cmd) {
        case CLI_INIT:
@@ -3783,18 +4209,20 @@ static char *handle_cli_submit(struct ast_cli_entry *e, int cmd, struct ast_cli_
                return CLI_SHOWUSAGE;
        }
 
-       if (!ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
-               ast_cli(a->fd, "Cannot submit CDR batch: CDR engine disabled.\n");
-               return CLI_SUCCESS;
+       mod_cfg = ao2_global_obj_ref(module_configs);
+       if (!mod_cfg) {
+               return CLI_FAILURE;
        }
 
-       if (ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
+       if (!ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
+               ast_cli(a->fd, "Cannot submit CDR batch: CDR engine disabled.\n");
+       } else if (!ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
                ast_cli(a->fd, "Cannot submit CDR batch: batch mode not enabled.\n");
-               return CLI_SUCCESS;
+       } else {
+               start_batch_mode();
+               ast_cli(a->fd, "Submitted CDRs to backend engines for processing.  This may take a while.\n");
        }
-
-       submit_unscheduled_batch();
-       ast_cli(a->fd, "Submitted CDRs to backend engines for processing.  This may take a while.\n");
+       ao2_cleanup(mod_cfg);
 
        return CLI_SUCCESS;
 }
@@ -3822,7 +4250,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)
@@ -3839,10 +4269,57 @@ static void finalize_batch_mode(void)
        ast_cdr_engine_term();
 }
 
-static int process_config(int reload)
+struct stasis_message_router *ast_cdr_message_router(void)
 {
-       RAII_VAR(struct module_config *, mod_cfg, module_config_alloc(), ao2_cleanup);
+       if (!stasis_router) {
+               return NULL;
+       }
+
+       ao2_bump(stasis_router);
+       return stasis_router;
+}
+
+/*!
+ * \brief Destroy the active Stasis subscriptions
+ */
+static void destroy_subscriptions(void)
+{
+       channel_subscription = stasis_forward_cancel(channel_subscription);
+       bridge_subscription = stasis_forward_cancel(bridge_subscription);
+       parking_subscription = stasis_forward_cancel(parking_subscription);
+}
 
+/*!
+ * \brief Create the Stasis subcriptions for CDRs
+ */
+static int create_subscriptions(void)
+{
+       if (!cdr_topic) {
+               return -1;
+       }
+
+       if (channel_subscription || bridge_subscription || parking_subscription) {
+               return 0;
+       }
+
+       channel_subscription = stasis_forward_all(ast_channel_topic_all_cached(), cdr_topic);
+       if (!channel_subscription) {
+               return -1;
+       }
+       bridge_subscription = stasis_forward_all(ast_bridge_topic_all_cached(), cdr_topic);
+       if (!bridge_subscription) {
+               return -1;
+       }
+       parking_subscription = stasis_forward_all(ast_parking_topic(), cdr_topic);
+       if (!parking_subscription) {
+               return -1;
+       }
+
+       return 0;
+}
+
+static int process_config(int reload)
+{
        if (!reload) {
                if (aco_info_init(&cfg_info)) {
                        return 1;
@@ -3858,41 +4335,46 @@ static int process_config(int reload)
                aco_option_register(&cfg_info, "scheduleronly", ACO_EXACT, general_options, DEFAULT_BATCH_SCHEDULER_ONLY, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, batch_settings.settings), BATCH_MODE_SCHEDULER_ONLY);
                aco_option_register(&cfg_info, "safeshutdown", ACO_EXACT, general_options, DEFAULT_BATCH_SAFE_SHUTDOWN, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, batch_settings.settings), BATCH_MODE_SAFE_SHUTDOWN);
                aco_option_register(&cfg_info, "size", ACO_EXACT, general_options, DEFAULT_BATCH_SIZE, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_cdr_config, batch_settings.size), 0, MAX_BATCH_SIZE);
-               aco_option_register(&cfg_info, "time", ACO_EXACT, general_options, DEFAULT_BATCH_TIME, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_cdr_config, batch_settings.time), 0, MAX_BATCH_TIME);
+               aco_option_register(&cfg_info, "time", ACO_EXACT, general_options, DEFAULT_BATCH_TIME, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_cdr_config, batch_settings.time), 1, MAX_BATCH_TIME);
        }
 
-       if (aco_process_config(&cfg_info, reload)) {
-               if (!mod_cfg) {
+       if (aco_process_config(&cfg_info, reload) == ACO_PROCESS_ERROR) {
+               struct module_config *mod_cfg;
+
+               if (reload) {
                        return 1;
                }
+
                /* If we couldn't process the configuration and this wasn't a reload,
                 * create a default config
                 */
-               if (!reload && !(aco_set_defaults(&general_option, "general", mod_cfg->general))) {
-                       ast_log(LOG_NOTICE, "Failed to process CDR configuration; using defaults\n");
-                       ao2_global_obj_replace_unref(module_configs, mod_cfg);
-                       return 0;
+               mod_cfg = module_config_alloc();
+               if (!mod_cfg
+                       || aco_set_defaults(&general_option, "general", mod_cfg->general)) {
+                       ao2_cleanup(mod_cfg);
+                       return 1;
                }
-               return 1;
+               ast_log(LOG_NOTICE, "Failed to process CDR configuration; using defaults\n");
+               ao2_global_obj_replace_unref(module_configs, mod_cfg);
+               cdr_set_debug_mode(mod_cfg);
+               ao2_cleanup(mod_cfg);
        }
 
        return 0;
 }
 
-static void cdr_engine_cleanup(void)
+static void cdr_engine_shutdown(void)
 {
-       channel_subscription = stasis_forward_cancel(channel_subscription);
-       bridge_subscription = stasis_forward_cancel(bridge_subscription);
-       parking_subscription = stasis_forward_cancel(parking_subscription);
        stasis_message_router_unsubscribe_and_join(stasis_router);
+       stasis_router = NULL;
+
        ao2_cleanup(cdr_topic);
        cdr_topic = NULL;
-}
 
-static void cdr_engine_shutdown(void)
-{
-       ao2_callback(active_cdrs_by_channel, OBJ_NODATA, cdr_object_dispatch_all_cb,
-               NULL);
+       STASIS_MESSAGE_TYPE_CLEANUP(cdr_sync_message_type);
+
+       ao2_callback(active_cdrs_master, 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);
@@ -3903,15 +4385,17 @@ static void cdr_engine_shutdown(void)
        aco_info_destroy(&cfg_info);
        ao2_global_obj_release(module_configs);
 
-       ao2_container_unregister("cdrs_by_channel");
-       ao2_ref(active_cdrs_by_channel, -1);
-       active_cdrs_by_channel = NULL;
+       ao2_container_unregister("cdrs_master");
+       ao2_cleanup(active_cdrs_master);
+       active_cdrs_master = 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)
 {
-       SCOPED_LOCK(batch, &cdr_batch_lock, ast_mutex_lock, ast_mutex_unlock);
-
        /* Only create the thread level portions once */
        if (cdr_thread == AST_PTHREADT_NULL) {
                ast_cond_init(&cdr_pending_cond, NULL);
@@ -3921,103 +4405,154 @@ static void cdr_enable_batch_mode(struct ast_cdr_config *config)
                }
        }
 
-       /* Kill the currently scheduled item */
-       AST_SCHED_DEL(sched, cdr_sched);
-       cdr_sched = ast_sched_add(sched, config->batch_settings.time * 1000, submit_scheduled_batch, NULL);
-       ast_log(LOG_NOTICE, "CDR batch mode logging enabled, first of either size %d or time %d seconds.\n",
+       /* Start the batching process */
+       start_batch_mode();
+
+       ast_log(LOG_NOTICE, "CDR batch mode logging enabled, first of either size %u or time %u seconds.\n",
                        config->batch_settings.size, config->batch_settings.time);
 }
 
 /*!
  * \internal
- * \brief Print channel object key (name).
+ * \brief Print master CDR container object.
  * \since 12.0.0
  *
- * \param v_obj A pointer to the object we want the key printed.
+ * \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_container_print_fn(void *v_obj, void *where, ao2_prnt_fn *prnt)
+static void cdr_master_print_fn(void *v_obj, void *where, ao2_prnt_fn *prnt)
 {
        struct cdr_object *cdr = v_obj;
        struct cdr_object *it_cdr;
+
        if (!cdr) {
                return;
        }
        for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
-               prnt(where, "Party A: %s; Party B: %s; Bridge %s\n", it_cdr->party_a.snapshot->name, it_cdr->party_b.snapshot ? it_cdr->party_b.snapshot->name : "<unknown>",
-                               it_cdr->bridge);
+               prnt(where, "Party A: %s; Party B: %s; Bridge %s\n",
+                       it_cdr->party_a.snapshot->name,
+                       it_cdr->party_b.snapshot ? it_cdr->party_b.snapshot->name : "<unknown>",
+                       it_cdr->bridge);
        }
 }
 
-int ast_cdr_engine_init(void)
+/*!
+ * \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)
 {
-       RAII_VAR(struct module_config *, mod_cfg, NULL, ao2_cleanup);
+       struct cdr_object *cdr = v_obj;
 
-       if (process_config(0)) {
-               return -1;
+       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 : "<unknown>",
+               cdr->bridge);
+}
 
-       active_cdrs_by_channel = ao2_container_alloc(NUM_CDR_BUCKETS,
-               cdr_object_channel_hash_fn, cdr_object_channel_cmp_fn);
-       if (!active_cdrs_by_channel) {
-               return -1;
-       }
-       ao2_container_register("cdrs_by_channel", active_cdrs_by_channel, cdr_container_print_fn);
+/*!
+ * \brief Checks if CDRs are enabled and enables/disables the necessary options
+ */
+static int cdr_toggle_runtime_options(void)
+{
+       struct module_config *mod_cfg;
 
-       cdr_topic = stasis_topic_create("cdr_engine");
-       if (!cdr_topic) {
-               return -1;
+       mod_cfg = ao2_global_obj_ref(module_configs);
+       if (mod_cfg
+               && ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
+               if (create_subscriptions()) {
+                       destroy_subscriptions();
+                       ast_log(AST_LOG_ERROR, "Failed to create Stasis subscriptions\n");
+                       ao2_cleanup(mod_cfg);
+                       return -1;
+               }
+               if (ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
+                       cdr_enable_batch_mode(mod_cfg->general);
+               } else {
+                       ast_log(LOG_NOTICE, "CDR simple logging enabled.\n");
+               }
+       } else {
+               destroy_subscriptions();
+               ast_log(LOG_NOTICE, "CDR logging disabled.\n");
        }
+       ao2_cleanup(mod_cfg);
 
-       channel_subscription = stasis_forward_all(ast_channel_topic_all_cached(), cdr_topic);
-       if (!channel_subscription) {
-               return -1;
-       }
-       bridge_subscription = stasis_forward_all(ast_bridge_topic_all_cached(), cdr_topic);
-       if (!bridge_subscription) {
-               return -1;
+       return mod_cfg ? 0 : -1;
+}
+
+static int unload_module(void)
+{
+       destroy_subscriptions();
+
+       return 0;
+}
+
+static int load_module(void)
+{
+       if (process_config(0)) {
+               return AST_MODULE_LOAD_FAILURE;
        }
-       parking_subscription = stasis_forward_all(ast_parking_topic(), cdr_topic);
-       if (!parking_subscription) {
-               return -1;
+
+       cdr_topic = stasis_topic_create("cdr_engine");
+       if (!cdr_topic) {
+               return AST_MODULE_LOAD_FAILURE;
        }
 
        stasis_router = stasis_message_router_create(cdr_topic);
        if (!stasis_router) {
-               return -1;
+               return AST_MODULE_LOAD_FAILURE;
+       }
+       stasis_message_router_set_congestion_limits(stasis_router, -1,
+               10 * AST_TASKPROCESSOR_HIGH_WATER_LEVEL);
+
+       if (STASIS_MESSAGE_TYPE_INIT(cdr_sync_message_type)) {
+               return AST_MODULE_LOAD_FAILURE;
        }
+
        stasis_message_router_add_cache_update(stasis_router, ast_channel_snapshot_type(), handle_channel_cache_message, NULL);
        stasis_message_router_add(stasis_router, ast_channel_dial_type(), handle_dial_message, NULL);
        stasis_message_router_add(stasis_router, ast_channel_entered_bridge_type(), handle_bridge_enter_message, NULL);
        stasis_message_router_add(stasis_router, ast_channel_left_bridge_type(), handle_bridge_leave_message, NULL);
        stasis_message_router_add(stasis_router, ast_parked_call_type(), handle_parked_call_message, NULL);
+       stasis_message_router_add(stasis_router, cdr_sync_message_type(), handle_cdr_sync_message, NULL);
+
+       active_cdrs_master = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0,
+               NUM_CDR_BUCKETS, cdr_master_hash_fn, NULL, cdr_master_cmp_fn);
+       if (!active_cdrs_master) {
+               return AST_MODULE_LOAD_FAILURE;
+       }
+       ao2_container_register("cdrs_master", active_cdrs_master, cdr_master_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 AST_MODULE_LOAD_FAILURE;
+       }
+       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");
-               return -1;
+               return AST_MODULE_LOAD_FAILURE;
        }
 
        ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands));
-       ast_register_cleanup(cdr_engine_cleanup);
        ast_register_atexit(cdr_engine_shutdown);
 
-       mod_cfg = ao2_global_obj_ref(module_configs);
-
-       if (ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
-               if (ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
-                       cdr_enable_batch_mode(mod_cfg->general);
-               } else {
-                       ast_log(LOG_NOTICE, "CDR simple logging enabled.\n");
-               }
-       } else {
-               ast_log(LOG_NOTICE, "CDR logging disabled.\n");
-       }
-
-       return 0;
+       return cdr_toggle_runtime_options() ? AST_MODULE_LOAD_FAILURE : AST_MODULE_LOAD_SUCCESS;
 }
 
 void ast_cdr_engine_term(void)
@@ -4030,42 +4565,68 @@ void ast_cdr_engine_term(void)
        if (!mod_cfg) {
                return;
        }
-       if (!ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
-               return;
+
+       if (cdr_sync_message_type()) {
+               void *payload;
+               struct stasis_message *message;
+
+               if (!stasis_router) {
+                       return;
+               }
+
+               /* Make sure we have the needed items */
+               payload = ao2_alloc(sizeof(*payload), NULL);
+               if (!payload) {
+                       return;
+               }
+
+               ast_debug(1, "CDR Engine termination request received; waiting on messages...\n");
+
+               message = stasis_message_create(cdr_sync_message_type(), payload);
+               if (message) {
+                       stasis_message_router_publish_sync(stasis_router, message);
+               }
+               ao2_cleanup(message);
+               ao2_cleanup(payload);
+       }
+
+       if (ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
+               cdr_submit_batch(ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SAFE_SHUTDOWN));
        }
-       cdr_submit_batch(ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SAFE_SHUTDOWN));
 }
 
-int ast_cdr_engine_reload(void)
+static int reload_module(void)
 {
-       RAII_VAR(struct module_config *, old_mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
-       RAII_VAR(struct module_config *, mod_cfg, NULL, ao2_cleanup);
+       struct module_config *old_mod_cfg;
+       struct module_config *mod_cfg;
 
-       if (process_config(1)) {
+       old_mod_cfg = ao2_global_obj_ref(module_configs);
+
+       if (!old_mod_cfg || process_config(1)) {
+               ao2_cleanup(old_mod_cfg);
                return -1;
        }
 
        mod_cfg = ao2_global_obj_ref(module_configs);
-
-       if (!ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED) ||
-                       !(ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE))) {
+       if (!mod_cfg
+               || !ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)
+               || !ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
                /* If batch mode used to be enabled, finalize the batch */
                if (ast_test_flag(&old_mod_cfg->general->settings, CDR_BATCHMODE)) {
                        finalize_batch_mode();
                }
        }
+       ao2_cleanup(mod_cfg);
 
-       if (ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
-               if (!ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
-                       ast_log(LOG_NOTICE, "CDR simple logging enabled.\n");
-               } else {
-                       cdr_enable_batch_mode(mod_cfg->general);
-               }
-       } else {
-               ast_log(LOG_NOTICE, "CDR logging disabled, data will be lost.\n");
-       }
-
-       return 0;
+       ao2_cleanup(old_mod_cfg);
+       return cdr_toggle_runtime_options();
 }
 
-
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "CDR Engine",
+       .support_level = AST_MODULE_SUPPORT_CORE,
+       .load = load_module,
+       .unload = unload_module,
+       .reload = reload_module,
+       .load_pri = AST_MODPRI_CORE,
+       .requires = "extconfig",
+);