]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
Deadlocks dealing with dialplan hints during reload.
authorRichard Mudgett <rmudgett@digium.com>
Fri, 22 Jul 2011 15:44:58 +0000 (15:44 +0000)
committerRichard Mudgett <rmudgett@digium.com>
Fri, 22 Jul 2011 15:44:58 +0000 (15:44 +0000)
There are two remaining different deadlocks reported dealing with dialplan
hints.

The deadlock in ASTERISK-17666 is caused by invalid locking order in
ast_remove_hint().  The hints container must be locked before the hint
object.

The deadlock in ASTERISK-17760 is caused by a catch-22 situation in
handle_statechange().  The deadlock is caused by not having the conlock
before calling the watcher callbacks.  Unfortunately, having that lock
causes a different deadlock as reported in ASTERISK-16961.

* Fixed ast_remove_hint() locking order.

* Made handle_statechange() no longer call the watcher callbacks holding
any locks that matter.

* Made hint ao2 destructor do the watcher callbacks for extension
deactivation to guarantee that they get called.

* Fixed hint reference leak in ast_add_hint() if the callback container
constructor failed.

* Fixed hint reference leak in complete_core_show_hint() for every hint it
found for CLI tab completion.

* Adjusted locking in ast_merge_contexts_and_delete() for safety.

* Added context_merge_lock to prevent ast_merge_contexts_and_delete() and
handle_statechange() from interfering with each other.

* Fixed ast_change_hint() not taking into account that the extension is
used for the hash key.

(closes issue ASTERISK-17666)
Reported by: irroot
Tested by: irroot
JIRA SWP-3318

(closes issue ASTERISK-17760)
Reported by: Byron Clark
Tested by: irroot
JIRA SWP-3393

Review: https://reviewboard.asterisk.org/r/1313/

git-svn-id: https://origsvn.digium.com/svn/asterisk/branches/1.8@329299 65c4cc65-6c06-0410-ace0-fbb531ad65f3

main/pbx.c

index 869d678656486075b6531ab61953d8ab11ff3c24..a1f805cb0982a0aad4aef064b16841b682c3546e 100644 (file)
@@ -934,9 +934,17 @@ struct ast_state_cb {
  * See \ref AstExtState
  */
 struct ast_hint {
-       struct ast_exten *exten;        /*!< Extension */
-       int laststate;                  /*!< Last known state */
+       /*!
+        * \brief Hint extension
+        *
+        * \note
+        * Will never be NULL while the hint is in the hints container.
+        */
+       struct ast_exten *exten;
        struct ao2_container *callbacks; /*!< Callback container for this extension */
+       int laststate;                  /*!< Last known state */
+       char context_name[AST_MAX_CONTEXT];/*!< Context of destroyed hint extension. */
+       char exten_name[AST_MAX_EXTENSION];/*!< Extension of destroyed hint extension. */
 };
 
 /* --- Hash tables of various objects --------*/
@@ -1182,6 +1190,11 @@ static struct ast_hashtab *contexts_table = NULL;
  */
 AST_MUTEX_DEFINE_STATIC(conlock);
 
+/*!
+ * \brief Lock to hold off restructuring of hints by ast_merge_contexts_and_delete.
+ */
+AST_MUTEX_DEFINE_STATIC(context_merge_lock);
+
 static AST_RWLIST_HEAD_STATIC(apps, ast_app);
 
 static AST_RWLIST_HEAD_STATIC(switches, ast_switch);
@@ -4190,27 +4203,34 @@ enum ast_extension_states ast_devstate_to_extenstate(enum ast_device_state devst
        return AST_EXTENSION_NOT_INUSE;
 }
 
-/*! \brief Check state of extension by using hints */
-static int ast_extension_state2(struct ast_exten *e)
+static int ast_extension_state3(struct ast_str *hint_app)
 {
-       struct ast_str *hint = ast_str_thread_get(&extensionstate_buf, 16);
-       char *cur, *rest;
+       char *cur;
+       char *rest;
        struct ast_devstate_aggregate agg;
 
-       if (!e)
-               return -1;
+       /* One or more devices separated with a & character */
+       rest = ast_str_buffer(hint_app);
 
        ast_devstate_aggregate_init(&agg);
+       while ((cur = strsep(&rest, "&"))) {
+               ast_devstate_aggregate_add(&agg, ast_device_state(cur));
+       }
 
-       ast_str_set(&hint, 0, "%s", ast_get_extension_app(e));
+       return ast_devstate_to_extenstate(ast_devstate_aggregate_result(&agg));
+}
 
-       rest = ast_str_buffer(hint);    /* One or more devices separated with a & character */
+/*! \brief Check state of extension by using hints */
+static int ast_extension_state2(struct ast_exten *e)
+{
+       struct ast_str *hint_app = ast_str_thread_get(&extensionstate_buf, 32);
 
-       while ( (cur = strsep(&rest, "&")) ) {
-               ast_devstate_aggregate_add(&agg, ast_device_state(cur));
+       if (!e || !hint_app) {
+               return -1;
        }
 
-       return ast_devstate_to_extenstate(ast_devstate_aggregate_result(&agg));
+       ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(e));
+       return ast_extension_state3(hint_app);
 }
 
 /*! \brief Return extension_state as string */
@@ -4251,71 +4271,104 @@ int ast_extension_state(struct ast_channel *c, const char *context, const char *
 static int handle_statechange(void *datap)
 {
        struct ast_hint *hint;
-       struct ast_str *str;
+       struct ast_str *hint_app;
        struct statechange *sc = datap;
        struct ao2_iterator i;
        struct ao2_iterator cb_iter;
+       char context_name[AST_MAX_CONTEXT];
+       char exten_name[AST_MAX_EXTENSION];
 
-       if (!(str = ast_str_create(1024))) {
+       hint_app = ast_str_create(1024);
+       if (!hint_app) {
                return -1;
        }
 
+       ast_mutex_lock(&context_merge_lock);/* Hold off ast_merge_contexts_and_delete */
        i = ao2_iterator_init(hints, 0);
        for (hint = ao2_iterator_next(&i); hint; ao2_ref(hint, -1), hint = ao2_iterator_next(&i)) {
                struct ast_state_cb *state_cb;
                char *cur, *parse;
                int state;
 
-               ast_str_set(&str, 0, "%s", ast_get_extension_app(hint->exten));
-               parse = str->str;
+               ao2_lock(hint);
+               if (!hint->exten) {
+                       /* The extension has already been destroyed */
+                       ao2_unlock(hint);
+                       continue;
+               }
 
-               while ( (cur = strsep(&parse, "&")) ) {
+               /* Does this hint monitor the device that changed state? */
+               ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(hint->exten));
+               parse = hint_app->str;
+               while ((cur = strsep(&parse, "&"))) {
                        if (!strcasecmp(cur, sc->dev)) {
+                               /* The hint monitors the device. */
                                break;
                        }
                }
                if (!cur) {
+                       /* The hint does not monitor the device. */
+                       ao2_unlock(hint);
                        continue;
                }
 
-               /* Get device state for this hint */
-               state = ast_extension_state2(hint->exten);
-               if ((state == -1) || (state == hint->laststate)) {
-                       continue;
-               }
-
-               /* Device state changed since last check - notify the watchers */
+               /*
+                * Save off strings in case the hint extension gets destroyed
+                * while we are notifying the watchers.
+                */
+               ast_copy_string(context_name,
+                       ast_get_context_name(ast_get_extension_context(hint->exten)),
+                       sizeof(context_name));
+               ast_copy_string(exten_name, ast_get_extension_name(hint->exten),
+                       sizeof(exten_name));
 
-               ao2_lock(hints);
-               ao2_lock(hint);
+               ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(hint->exten));
+               ao2_unlock(hint);
 
-               if (hint->exten == NULL) {
-                       /* the extension has been destroyed */
-                       ao2_unlock(hint);
-                       ao2_unlock(hints);
+               /*
+                * Get device state for this hint.
+                *
+                * NOTE: We cannot hold any locks while determining the hint
+                * device state or notifying the watchers without causing a
+                * deadlock.  (conlock, hints, and hint)
+                */
+               state = ast_extension_state3(hint_app);
+               if (state == hint->laststate) {
                        continue;
                }
 
+               /* Device state changed since last check - notify the watchers. */
+               hint->laststate = state;        /* record we saw the change */
+
                /* For general callbacks */
+               ao2_lock(statecbs);
                cb_iter = ao2_iterator_init(statecbs, 0);
-               for (state_cb = ao2_iterator_next(&cb_iter); state_cb; ao2_ref(state_cb, -1), state_cb = ao2_iterator_next(&cb_iter)) {
-                       state_cb->callback(hint->exten->parent->name, hint->exten->exten, state, state_cb->data);
+               for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
+                       void *data;
+
+                       /*
+                        * Protect the data ptr because it could get updated by
+                        * ast_extension_state_add().
+                        */
+                       data = state_cb->data;
+                       ao2_unlock(statecbs);
+                       state_cb->callback(context_name, exten_name, state, data);
+                       ao2_lock(statecbs);
                }
+               ao2_unlock(statecbs);
                ao2_iterator_destroy(&cb_iter);
 
                /* For extension callbacks */
                cb_iter = ao2_iterator_init(hint->callbacks, 0);
-               for (state_cb = ao2_iterator_next(&cb_iter); state_cb; ao2_ref(state_cb, -1), state_cb = ao2_iterator_next(&cb_iter)) {
-                       state_cb->callback(hint->exten->parent->name, hint->exten->exten, state, state_cb->data);
+               for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
+                       state_cb->callback(context_name, exten_name, state, state_cb->data);
                }
                ao2_iterator_destroy(&cb_iter);
-
-               hint->laststate = state;        /* record we saw the change */
-               ao2_unlock(hint);
-               ao2_unlock(hints);
        }
        ao2_iterator_destroy(&i);
-       ast_free(str);
+       ast_mutex_unlock(&context_merge_lock);
+
+       ast_free(hint_app);
        ast_free(sc);
        return 0;
 }
@@ -4327,32 +4380,33 @@ int ast_extension_state_add(const char *context, const char *exten,
        struct ast_hint *hint;
        struct ast_state_cb *state_cb;
        struct ast_exten *e;
+       int id;
 
        /* If there's no context and extension:  add callback to statecbs list */
        if (!context && !exten) {
-               ao2_lock(hints);
+               /* Prevent multiple adds from adding the same callback at the same time. */
+               ao2_lock(statecbs);
 
                state_cb = ao2_find(statecbs, callback, 0);
                if (state_cb) {
                        state_cb->data = data;
                        ao2_ref(state_cb, -1);
-                       ao2_unlock(hints);
+                       ao2_unlock(statecbs);
                        return 0;
                }
 
                /* Now insert the callback */
                if (!(state_cb = ao2_alloc(sizeof(*state_cb), NULL))) {
-                       ao2_unlock(hints);
+                       ao2_unlock(statecbs);
                        return -1;
                }
                state_cb->id = 0;
                state_cb->callback = callback;
                state_cb->data = data;
-
                ao2_link(statecbs, state_cb);
-               ao2_ref(state_cb, -1);
 
-               ao2_unlock(hints);
+               ao2_ref(state_cb, -1);
+               ao2_unlock(statecbs);
                return 0;
        }
 
@@ -4379,31 +4433,31 @@ int ast_extension_state_add(const char *context, const char *exten,
                }
        }
 
-       /* Find the hint in the list of hints */
+       /* Find the hint in the hints container */
+       ao2_lock(hints);/* Locked to hold off ast_merge_contexts_and_delete */
        hint = ao2_find(hints, e, 0);
-
        if (!hint) {
+               ao2_unlock(hints);
                return -1;
        }
 
        /* Now insert the callback in the callback list  */
        if (!(state_cb = ao2_alloc(sizeof(*state_cb), NULL))) {
                ao2_ref(hint, -1);
+               ao2_unlock(hints);
                return -1;
        }
-
-       state_cb->id = stateid++;               /* Unique ID for this callback */
+       id = stateid++;         /* Unique ID for this callback */
+       state_cb->id = id;
        state_cb->callback = callback;  /* Pointer to callback routine */
        state_cb->data = data;          /* Data for the callback */
-
-       ao2_lock(hint);
        ao2_link(hint->callbacks, state_cb);
-       ao2_ref(state_cb, -1);
-       ao2_unlock(hint);
 
+       ao2_ref(state_cb, -1);
        ao2_ref(hint, -1);
+       ao2_unlock(hints);
 
-       return state_cb->id;
+       return id;
 }
 
 /*! \brief Remove a watcher from the callback list */
@@ -4427,33 +4481,29 @@ int ast_extension_state_del(int id, ast_state_cb_type callback)
        struct ast_state_cb *p_cur = NULL;
        int ret = -1;
 
-       if (!id && !callback) {
-               return -1;
-       }
-
        if (!id) {      /* id == 0 is a callback without extension */
-               ao2_lock(hints);
+               if (!callback) {
+                       return ret;
+               }
                p_cur = ao2_find(statecbs, callback, OBJ_UNLINK);
                if (p_cur) {
                        ret = 0;
                        ao2_ref(p_cur, -1);
                }
-               ao2_unlock(hints);
        } else { /* callback with extension, find the callback based on ID */
                struct ast_hint *hint;
 
+               ao2_lock(hints);/* Locked to hold off ast_merge_contexts_and_delete */
                hint = ao2_callback(hints, 0, find_hint_by_cb_id, &id);
-
                if (hint) {
-                       ao2_lock(hint);
                        p_cur = ao2_find(hint->callbacks, &id, OBJ_UNLINK);
                        if (p_cur) {
                                ret = 0;
                                ao2_ref(p_cur, -1);
                        }
-                       ao2_unlock(hint);
                        ao2_ref(hint, -1);
                }
+               ao2_unlock(hints);
        }
 
        return ret;
@@ -4468,91 +4518,158 @@ static int hint_id_cmp(void *obj, void *arg, int flags)
        return (cb->id == *id) ? CMP_MATCH | CMP_STOP : 0;
 }
 
-/*! \brief Add hint to hint list, check initial extension state */
-static int ast_add_hint(struct ast_exten *e)
+/*!
+ * \internal
+ * \brief Destroy the given hint object.
+ *
+ * \param obj Hint to destroy.
+ *
+ * \return Nothing
+ */
+static void destroy_hint(void *obj)
 {
-       struct ast_hint *hint;
+       struct ast_hint *hint = obj;
 
-       if (!e) {
-               return -1;
-       }
+       if (hint->callbacks) {
+               struct ast_state_cb *state_cb;
+               const char *context_name;
+               const char *exten_name;
 
-       /* Search if hint exists, do nothing */ 
-       hint = ao2_find(hints, e, 0);
-       if (hint) {
-               ast_debug(2, "HINTS: Not re-adding existing hint %s: %s\n", ast_get_extension_name(e), ast_get_extension_app(e));
-               ao2_ref(hint, -1);
-               return -1;
+               if (hint->exten) {
+                       context_name = ast_get_context_name(ast_get_extension_context(hint->exten));
+                       exten_name = ast_get_extension_name(hint->exten);
+                       hint->exten = NULL;
+               } else {
+                       /* The extension has already been destroyed */
+                       context_name = hint->context_name;
+                       exten_name = hint->exten_name;
+               }
+               while ((state_cb = ao2_callback(hint->callbacks, OBJ_UNLINK, NULL, NULL))) {
+                       /* Notify with -1 and remove all callbacks */
+                       /* NOTE: The casts will not be needed for v1.10 and later */
+                       state_cb->callback((char *) context_name, (char *) exten_name,
+                               AST_EXTENSION_DEACTIVATED, state_cb->data);
+                       ao2_ref(state_cb, -1);
+               }
+               ao2_ref(hint->callbacks, -1);
        }
+}
 
-       ast_debug(2, "HINTS: Adding hint %s: %s\n", ast_get_extension_name(e), ast_get_extension_app(e));
+/*! \brief Remove hint from extension */
+static int ast_remove_hint(struct ast_exten *e)
+{
+       /* Cleanup the Notifys if hint is removed */
+       struct ast_hint *hint;
 
-       if (!(hint = ao2_alloc(sizeof(*hint), NULL))) {
+       if (!e) {
                return -1;
        }
-       if (!(hint->callbacks = ao2_container_alloc(1, NULL, hint_id_cmp))) {
+
+       hint = ao2_find(hints, e, OBJ_UNLINK);
+       if (!hint) {
                return -1;
        }
 
-       /* Initialize and insert new item at the top */
-       hint->exten = e;
-       hint->laststate = ast_extension_state2(e);
-
-       ao2_link(hints, hint);
+       /*
+        * The extension is being destroyed so we must save some
+        * information to notify that the extension is deactivated.
+        */
+       ao2_lock(hint);
+       ast_copy_string(hint->context_name,
+               ast_get_context_name(ast_get_extension_context(hint->exten)),
+               sizeof(hint->context_name));
+       ast_copy_string(hint->exten_name, ast_get_extension_name(hint->exten),
+               sizeof(hint->exten_name));
+       hint->exten = NULL;
+       ao2_unlock(hint);
 
        ao2_ref(hint, -1);
 
        return 0;
 }
 
-/*! \brief Change hint for an extension */
-static int ast_change_hint(struct ast_exten *oe, struct ast_exten *ne)
+/*! \brief Add hint to hint list, check initial extension state */
+static int ast_add_hint(struct ast_exten *e)
 {
-       struct ast_hint *hint;
+       struct ast_hint *hint_new;
+       struct ast_hint *hint_found;
 
-       hint = ao2_find(hints, oe, 0);
+       if (!e) {
+               return -1;
+       }
 
-       if (!hint) {
+       /*
+        * We must create the hint we wish to add before determining if
+        * it is already in the hints container to avoid possible
+        * deadlock when getting the current extension state.
+        */
+       hint_new = ao2_alloc(sizeof(*hint_new), destroy_hint);
+       if (!hint_new) {
                return -1;
        }
 
-       ao2_lock(hint);
-       hint->exten = ne;
-       ao2_unlock(hint);
-       ao2_ref(hint, -1);
+       /* Initialize new hint. */
+       hint_new->callbacks = ao2_container_alloc(1, NULL, hint_id_cmp);
+       if (!hint_new->callbacks) {
+               ao2_ref(hint_new, -1);
+               return -1;
+       }
+       hint_new->exten = e;
+       hint_new->laststate = ast_extension_state2(e);
+
+       /* Prevent multiple add hints from adding the same hint at the same time. */
+       ao2_lock(hints);
+
+       /* Search if hint exists, do nothing */
+       hint_found = ao2_find(hints, e, 0);
+       if (hint_found) {
+               ao2_ref(hint_found, -1);
+               ao2_unlock(hints);
+               ao2_ref(hint_new, -1);
+               ast_debug(2, "HINTS: Not re-adding existing hint %s: %s\n",
+                       ast_get_extension_name(e), ast_get_extension_app(e));
+               return -1;
+       }
+
+       /* Add new hint to the hints container */
+       ast_debug(2, "HINTS: Adding hint %s: %s\n",
+               ast_get_extension_name(e), ast_get_extension_app(e));
+       ao2_link(hints, hint_new);
+
+       ao2_unlock(hints);
+       ao2_ref(hint_new, -1);
 
        return 0;
 }
 
-/*! \brief Remove hint from extension */
-static int ast_remove_hint(struct ast_exten *e)
+/*! \brief Change hint for an extension */
+static int ast_change_hint(struct ast_exten *oe, struct ast_exten *ne)
 {
-       /* Cleanup the Notifys if hint is removed */
        struct ast_hint *hint;
-       struct ast_state_cb *state_cb;
 
-       if (!e) {
+       if (!oe || !ne) {
                return -1;
        }
 
-       hint = ao2_find(hints, e, 0);
+       ao2_lock(hints);/* Locked to hold off others while we move the hint around. */
 
+       /*
+        * Unlink the hint from the hints container as the extension
+        * name (which is the hash value) could change.
+        */
+       hint = ao2_find(hints, oe, OBJ_UNLINK);
        if (!hint) {
+               ao2_unlock(hints);
                return -1;
        }
-       ao2_lock(hint);
-
-       while ((state_cb = ao2_callback(hint->callbacks, OBJ_UNLINK, NULL, NULL))) {
-               /* Notify with -1 and remove all callbacks */
-               state_cb->callback(hint->exten->parent->name, hint->exten->exten,
-                       AST_EXTENSION_DEACTIVATED, state_cb->data);
-               ao2_ref(state_cb, -1);
-       }
 
-       hint->exten = NULL;
-       ao2_unlink(hints, hint);
-       ao2_ref(hint->callbacks, -1);
+       /* Update the hint and put it back in the hints container. */
+       ao2_lock(hint);
+       hint->exten = ne;
        ao2_unlock(hint);
+       ao2_link(hints, hint);
+
+       ao2_unlock(hints);
        ao2_ref(hint, -1);
 
        return 0;
@@ -5801,13 +5918,19 @@ static char *handle_show_hints(struct ast_cli_entry *e, int cmd, struct ast_cli_
 
        i = ao2_iterator_init(hints, 0);
        for (hint = ao2_iterator_next(&i); hint; ao2_ref(hint, -1), hint = ao2_iterator_next(&i)) {
-
+               ao2_lock(hint);
+               if (!hint->exten) {
+                       /* The extension has already been destroyed */
+                       ao2_unlock(hint);
+                       continue;
+               }
                watchers = ao2_container_count(hint->callbacks);
                ast_cli(a->fd, "   %20s@%-20.20s: %-20.20s  State:%-15.15s Watchers %2d\n",
                        ast_get_extension_name(hint->exten),
                        ast_get_context_name(ast_get_extension_context(hint->exten)),
                        ast_get_extension_app(hint->exten),
                        ast_extension_state2str(hint->laststate), watchers);
+               ao2_unlock(hint);
                num++;
        }
        ao2_iterator_destroy(&i);
@@ -5833,13 +5956,20 @@ static char *complete_core_show_hint(const char *line, const char *word, int pos
 
        /* walk through all hints */
        i = ao2_iterator_init(hints, 0);
-       for (hint = ao2_iterator_next(&i); hint; ao2_unlock(hint), ao2_ref(hint, -1), hint = ao2_iterator_next(&i)) {
+       for (hint = ao2_iterator_next(&i); hint; ao2_ref(hint, -1), hint = ao2_iterator_next(&i)) {
                ao2_lock(hint);
-               if (!strncasecmp(word, hint->exten ? ast_get_extension_name(hint->exten) : "", wordlen) && ++which > state) {
+               if (!hint->exten) {
+                       /* The extension has already been destroyed */
+                       ao2_unlock(hint);
+                       continue;
+               }
+               if (!strncasecmp(word, ast_get_extension_name(hint->exten), wordlen) && ++which > state) {
                        ret = ast_strdup(ast_get_extension_name(hint->exten));
                        ao2_unlock(hint);
+                       ao2_ref(hint, -1);
                        break;
                }
+               ao2_unlock(hint);
        }
        ao2_iterator_destroy(&i);
 
@@ -5875,9 +6005,14 @@ static char *handle_show_hint(struct ast_cli_entry *e, int cmd, struct ast_cli_a
        
        extenlen = strlen(a->argv[3]);
        i = ao2_iterator_init(hints, 0);
-       for (hint = ao2_iterator_next(&i); hint; ao2_unlock(hint), ao2_ref(hint, -1), hint = ao2_iterator_next(&i)) {
+       for (hint = ao2_iterator_next(&i); hint; ao2_ref(hint, -1), hint = ao2_iterator_next(&i)) {
                ao2_lock(hint);
-               if (!strncasecmp(hint->exten ? ast_get_extension_name(hint->exten) : "", a->argv[3], extenlen)) {
+               if (!hint->exten) {
+                       /* The extension has already been destroyed */
+                       ao2_unlock(hint);
+                       continue;
+               }
+               if (!strncasecmp(ast_get_extension_name(hint->exten), a->argv[3], extenlen)) {
                        watchers = ao2_container_count(hint->callbacks);
                        ast_cli(a->fd, "   %20s@%-20.20s: %-20.20s  State:%-15.15s Watchers %2d\n",
                                ast_get_extension_name(hint->exten),
@@ -5886,6 +6021,7 @@ static char *handle_show_hint(struct ast_cli_entry *e, int cmd, struct ast_cli_a
                                ast_extension_state2str(hint->laststate), watchers);
                        num++;
                }
+               ao2_unlock(hint);
        }
        ao2_iterator_destroy(&i);
        if (!num)
@@ -7073,7 +7209,7 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
        struct ast_context *oldcontextslist;
        struct ast_hashtab *oldtable;
        struct store_hints store = AST_LIST_HEAD_INIT_VALUE;
-       struct store_hint *this;
+       struct store_hint *saved_hint;
        struct ast_hint *hint;
        struct ast_exten *exten;
        int length;
@@ -7098,6 +7234,7 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
         */
 
        begintime = ast_tvnow();
+       ast_mutex_lock(&context_merge_lock);/* Serialize ast_merge_contexts_and_delete */
        ast_rdlock_contexts();
        iter = ast_hashtab_start_traversal(contexts_table);
        while ((tmp = ast_hashtab_next(iter))) {
@@ -7112,31 +7249,36 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
        i = ao2_iterator_init(hints, AO2_ITERATOR_DONTLOCK);
        for (hint = ao2_iterator_next(&i); hint; ao2_ref(hint, -1), hint = ao2_iterator_next(&i)) {
                if (ao2_container_count(hint->callbacks)) {
-
-                       length = strlen(hint->exten->exten) + strlen(hint->exten->parent->name) + 2 + sizeof(*this);
-                       if (!(this = ast_calloc(1, length))) {
+                       ao2_lock(hint);
+                       if (!hint->exten) {
+                               /* The extension has already been destroyed. (Should never happen here) */
+                               ao2_unlock(hint);
                                continue;
                        }
-                       ao2_lock(hint);
 
-                       if (hint->exten == NULL) {
+                       length = strlen(hint->exten->exten) + strlen(hint->exten->parent->name) + 2
+                               + sizeof(*saved_hint);
+                       if (!(saved_hint = ast_calloc(1, length))) {
                                ao2_unlock(hint);
                                continue;
                        }
 
-                       /* this removes all the callbacks from the hint into 'this'. */
+                       /* This removes all the callbacks from the hint into saved_hint. */
                        while ((thiscb = ao2_callback(hint->callbacks, OBJ_UNLINK, NULL, NULL))) {
-                               AST_LIST_INSERT_TAIL(&this->callbacks, thiscb, entry);
-                               /* We intentionally do not unref thiscb to account for the non-ao2 reference in this->callbacks */
+                               AST_LIST_INSERT_TAIL(&saved_hint->callbacks, thiscb, entry);
+                               /*
+                                * We intentionally do not unref thiscb to account for the
+                                * non-ao2 reference in saved_hint->callbacks
+                                */
                        }
 
-                       this->laststate = hint->laststate;
-                       this->context = this->data;
-                       strcpy(this->data, hint->exten->parent->name);
-                       this->exten = this->data + strlen(this->context) + 1;
-                       strcpy(this->exten, hint->exten->exten);
+                       saved_hint->laststate = hint->laststate;
+                       saved_hint->context = saved_hint->data;
+                       strcpy(saved_hint->data, hint->exten->parent->name);
+                       saved_hint->exten = saved_hint->data + strlen(saved_hint->context) + 1;
+                       strcpy(saved_hint->exten, hint->exten->exten);
                        ao2_unlock(hint);
-                       AST_LIST_INSERT_HEAD(&store, this, list);
+                       AST_LIST_INSERT_HEAD(&store, saved_hint, list);
                }
        }
 
@@ -7148,48 +7290,56 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
        contexts_table = exttable;
        contexts = *extcontexts;
 
-       /* restore the watchers for hints that can be found; notify those that
-          cannot be restored
-       */
-       while ((this = AST_LIST_REMOVE_HEAD(&store, list))) {
+       /*
+        * Restore the watchers for hints that can be found; notify
+        * those that cannot be restored.
+        */
+       while ((saved_hint = AST_LIST_REMOVE_HEAD(&store, list))) {
                struct pbx_find_info q = { .stacklen = 0 };
-               exten = pbx_find_extension(NULL, NULL, &q, this->context, this->exten, PRIORITY_HINT, NULL, "", E_MATCH);
-               /* If this is a pattern, dynamically create a new extension for this
+
+               exten = pbx_find_extension(NULL, NULL, &q, saved_hint->context, saved_hint->exten,
+                       PRIORITY_HINT, NULL, "", E_MATCH);
+               /*
+                * If this is a pattern, dynamically create a new extension for this
                 * particular match.  Note that this will only happen once for each
                 * individual extension, because the pattern will no longer match first.
                 */
                if (exten && exten->exten[0] == '_') {
-                       ast_add_extension_nolock(exten->parent->name, 0, this->exten, PRIORITY_HINT, NULL,
-                               0, exten->app, ast_strdup(exten->data), ast_free_ptr, exten->registrar);
+                       ast_add_extension_nolock(exten->parent->name, 0, saved_hint->exten,
+                               PRIORITY_HINT, NULL, 0, exten->app, ast_strdup(exten->data), ast_free_ptr,
+                               exten->registrar);
                        /* rwlocks are not recursive locks */
-                       exten = ast_hint_extension_nolock(NULL, this->context, this->exten);
+                       exten = ast_hint_extension_nolock(NULL, saved_hint->context,
+                               saved_hint->exten);
                }
 
-               /* Find the hint in the list of hints */
-               hint = ao2_find(hints, exten, 0);
-               if (!exten || !hint) {
+               /* Find the hint in the hints container */
+               hint = exten ? ao2_find(hints, exten, 0) : NULL;
+               if (!hint) {
                        /* this hint has been removed, notify the watchers */
-                       while ((thiscb = AST_LIST_REMOVE_HEAD(&this->callbacks, entry))) {
-                               thiscb->callback(this->context, this->exten, AST_EXTENSION_REMOVED, thiscb->data);
-                               ao2_ref(thiscb, -1);  /* Ref that we added when putting into this->callbacks */
+                       while ((thiscb = AST_LIST_REMOVE_HEAD(&saved_hint->callbacks, entry))) {
+                               thiscb->callback(saved_hint->context, saved_hint->exten,
+                                       AST_EXTENSION_REMOVED, thiscb->data);
+                               /* Ref that we added when putting into saved_hint->callbacks */
+                               ao2_ref(thiscb, -1);
                        }
                } else {
                        ao2_lock(hint);
-                       while ((thiscb = AST_LIST_REMOVE_HEAD(&this->callbacks, entry))) {
+                       while ((thiscb = AST_LIST_REMOVE_HEAD(&saved_hint->callbacks, entry))) {
                                ao2_link(hint->callbacks, thiscb);
-                               ao2_ref(thiscb, -1);  /* Ref that we added when putting into this->callbacks */
+                               /* Ref that we added when putting into saved_hint->callbacks */
+                               ao2_ref(thiscb, -1);
                        }
-                       hint->laststate = this->laststate;
+                       hint->laststate = saved_hint->laststate;
                        ao2_unlock(hint);
-               }
-               ast_free(this);
-               if (hint) {
                        ao2_ref(hint, -1);
                }
+               ast_free(saved_hint);
        }
 
        ao2_unlock(hints);
        ast_unlock_contexts();
+       ast_mutex_unlock(&context_merge_lock);
        endlocktime = ast_tvnow();
 
        /*
@@ -10280,11 +10430,18 @@ char *ast_complete_applications(const char *line, const char *word, int state)
 static int hint_hash(const void *obj, const int flags)
 {
        const struct ast_hint *hint = obj;
+       const char *exten_name;
+       int res;
 
-       int res = -1;
-
-       if (ast_get_extension_name(hint->exten)) {
-               res = ast_str_case_hash(ast_get_extension_name(hint->exten));
+       exten_name = ast_get_extension_name(hint->exten);
+       if (ast_strlen_zero(exten_name)) {
+               /*
+                * If the exten or extension name isn't set, return 0 so that
+                * the ao2_find() search will start in the first bucket.
+                */
+               res = 0;
+       } else {
+               res = ast_str_case_hash(exten_name);
        }
 
        return res;