Index: main/pbx.c =================================================================== --- main/pbx.c (revision 327043) +++ main/pbx.c (working copy) @@ -934,7 +934,15 @@ * See \ref AstExtState */ struct ast_hint { - struct ast_exten *exten; /*!< Extension */ + /*! + * \brief Hint extension + * + * \note + * Will never be NULL outside of the ao2 destructor. It will be + * NULL in the destructor only if the callback container + * constructor failed while the hint was being constructed. + */ + struct ast_exten *exten; int laststate; /*!< Last known state */ struct ao2_container *callbacks; /*!< Callback container for this extension */ }; @@ -1182,6 +1190,11 @@ */ 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); @@ -4252,11 +4265,14 @@ 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))) { 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; @@ -4281,37 +4297,38 @@ continue; } - /* Device state changed since last check - notify the watchers */ - - ao2_lock(hints); + /* + * Device state changed since last check - notify the watchers. + * + * NOTE: We cannot hold any locks while notifying the watchers without + * causing a deadlock. (conlock, hints, and hint) + */ ao2_lock(hint); + 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)); + hint->laststate = state; /* record we saw the change */ + ao2_unlock(hint); - if (hint->exten == NULL) { - /* the extension has been destroyed */ - ao2_unlock(hint); - ao2_unlock(hints); - continue; - } - /* For general callbacks */ 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); + state_cb->callback(context_name, exten_name, state, state_cb->data); } 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); + 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_mutex_unlock(&context_merge_lock); + ast_free(str); ast_free(sc); return 0; @@ -4324,32 +4341,33 @@ 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_link(statecbs, state_cb); ao2_ref(state_cb, -1); - - ao2_unlock(hints); + ao2_unlock(statecbs); return 0; } @@ -4376,31 +4394,31 @@ } } - /* 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_link(hint->callbacks, state_cb); - ao2_lock(hint); - ao2_link(hint->callbacks, state_cb); ao2_ref(state_cb, -1); - ao2_unlock(hint); - ao2_ref(hint, -1); + ao2_unlock(hints); - return state_cb->id; + return id; } /*! \brief Remove a watcher from the callback list */ @@ -4429,28 +4447,25 @@ } if (!id) { /* id == 0 is a callback without extension */ - ao2_lock(hints); 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; @@ -4465,6 +4480,56 @@ return (cb->id == *id) ? CMP_MATCH | CMP_STOP : 0; } +/*! + * \internal + * \brief Destroy the given hint object. + * + * \param obj Hint to destroy. + * + * \return Nothing + */ +static void destroy_hint(void *obj) +{ + struct ast_hint *hint = obj; + + /* + * hint->exten will never be NULL unless the hint->callbacks + * container constructor failed while the hint was being + * constructed. + */ + if (hint->callbacks) { + struct ast_state_cb *state_cb; + + 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); + } + ao2_ref(hint->callbacks, -1); + } +} + +/*! \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 (!e) { + return -1; + } + + hint = ao2_find(hints, e, OBJ_UNLINK); + if (!hint) { + return -1; + } + + ao2_ref(hint, -1); + + return 0; +} + /*! \brief Add hint to hint list, check initial extension state */ static int ast_add_hint(struct ast_exten *e) { @@ -4474,30 +4539,37 @@ return -1; } + /* Prevent multiple add hints from adding the same hint at the same time. */ + ao2_lock(hints); + /* 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); + ao2_unlock(hints); return -1; } ast_debug(2, "HINTS: Adding hint %s: %s\n", ast_get_extension_name(e), ast_get_extension_app(e)); - if (!(hint = ao2_alloc(sizeof(*hint), NULL))) { + if (!(hint = ao2_alloc(sizeof(*hint), destroy_hint))) { + ao2_unlock(hints); return -1; } if (!(hint->callbacks = ao2_container_alloc(1, NULL, hint_id_cmp))) { + ao2_ref(hint, -1); + ao2_unlock(hints); return -1; } - /* Initialize and insert new item at the top */ + /* Initialize and add new hint to the hints container */ hint->exten = e; hint->laststate = ast_extension_state2(e); - ao2_link(hints, hint); ao2_ref(hint, -1); + ao2_unlock(hints); return 0; } @@ -4507,9 +4579,13 @@ { struct ast_hint *hint; + if (!oe || !ne) { + return -1; + } + ao2_lock(hints);/* Locked to hold off ast_merge_contexts_and_delete */ hint = ao2_find(hints, oe, 0); - if (!hint) { + ao2_unlock(hints); return -1; } @@ -4517,45 +4593,12 @@ hint->exten = ne; ao2_unlock(hint); ao2_ref(hint, -1); + ao2_unlock(hints); return 0; } -/*! \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; - struct ast_state_cb *state_cb; - if (!e) { - return -1; - } - - hint = ao2_find(hints, e, 0); - - if (!hint) { - 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); - ao2_unlock(hint); - ao2_ref(hint, -1); - - return 0; -} - - /*! \brief Get hint for channel */ int ast_get_hint(char *hint, int hintsize, char *name, int namesize, struct ast_channel *c, const char *context, const char *exten) { @@ -5798,13 +5841,14 @@ 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); 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); @@ -5830,13 +5874,15 @@ /* 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) { ret = ast_strdup(ast_get_extension_name(hint->exten)); ao2_unlock(hint); + ao2_ref(hint, -1); break; } + ao2_unlock(hint); } ao2_iterator_destroy(&i); @@ -5872,7 +5918,7 @@ 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)) { watchers = ao2_container_count(hint->callbacks); @@ -5883,6 +5929,7 @@ ast_extension_state2str(hint->laststate), watchers); num++; } + ao2_unlock(hint); } ao2_iterator_destroy(&i); if (!num) @@ -7070,7 +7117,7 @@ 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; @@ -7095,6 +7142,7 @@ */ 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))) { @@ -7109,31 +7157,31 @@ 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))) { - 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); } } @@ -7145,41 +7193,50 @@ 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 */ + /* Find the hint in the hints container */ hint = ao2_find(hints, exten, 0); if (!exten || !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); + ast_free(saved_hint); if (hint) { ao2_ref(hint, -1); } @@ -7187,6 +7244,7 @@ ao2_unlock(hints); ast_unlock_contexts(); + ast_mutex_unlock(&context_merge_lock); endlocktime = ast_tvnow(); /*