Merged revisions 294639 via svnmerge from

https://origsvn.digium.com/svn/asterisk/branches/1.6.2

................
  r294639 | jpeeler | 2010-11-11 13:31:00 -0600 (Thu, 11 Nov 2010) | 53 lines
  
  Merged revisions 294384 via svnmerge from 
  https://origsvn.digium.com/svn/asterisk/branches/1.4
  
  ........
    r294384 | jpeeler | 2010-11-09 11:37:59 -0600 (Tue, 09 Nov 2010) | 47 lines
    
    Fix a deadlock in device state change processing.
    
    Copied from some notes from the original author (Russell):
    
    Deadlock scenario:
    Thread 1: device state change thread
      Holds - rdlock on contexts
      Holds - hints lock
      Waiting on channels container lock
    
    Thread 2: SIP monitor thread
      Holds the "iflock"
      Holds a sip_pvt lock
      Holds channel container lock
      Waiting for a channel lock
    
    Thread 3: A channel thread (chan_local in this case)
      Holds 2 channel locks acquired within app_dial
      Holds a 3rd channel lock it got inside of chan_local
      Holds a local_pvt lock
      Waiting on a rdlock of the contexts lock
    
    A bunch of other threads waiting on a wrlock of the contexts lock
    
    
    To address this deadlock, some locking order rules must be put in place and
    enforced. Existing relevant rules:
    
    1) channel lock before a pvt lock
    2) contexts lock before hints lock
    3) channels container before a channel
    
    What's missing is some enforcement of the order when you involve more than any
    two. To fix this problem, I put in some code that ensures that (at least in the
    code paths involved in this bug) the locks in (3) come before the locks in (2).
    To change the operation of thread 1 to comply, I converted the storage of hints
    to an astobj2 container. This allows processing of hints without holding the
    hints container lock. So, in the code path that led to thread 1's state, it no
    longer holds either the contexts or hints lock while it attempts to lock the
    channels container.
    
    (closes issue #18165)
    Reported by: antonio
    
    ABE-2583
  ........
................


git-svn-id: https://origsvn.digium.com/svn/asterisk/branches/1.8@294640 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
Jeff Peeler
2010-11-11 19:42:06 +00:00
parent c6973b1187
commit aafc5f4e99
3 changed files with 207 additions and 128 deletions

View File

@@ -78,6 +78,7 @@ int __ast_fdleak_dup(int oldfd, const char *file, int line, const char *func);
int ast_set_priority(int); /*!< Provided by asterisk.c */ int ast_set_priority(int); /*!< Provided by asterisk.c */
int ast_fd_init(void); /*!< Provided by astfd.c */ int ast_fd_init(void); /*!< Provided by astfd.c */
int ast_pbx_init(void); /*!< Provided by pbx.c */
/*! /*!
* \brief Register a function to be executed before Asterisk exits. * \brief Register a function to be executed before Asterisk exits.

View File

@@ -3201,6 +3201,7 @@ int main(int argc, char *argv[])
tdd_init(); tdd_init();
ast_tps_init(); ast_tps_init();
ast_fd_init(); ast_fd_init();
ast_pbx_init();
if (getenv("HOME")) if (getenv("HOME"))
snprintf(filename, sizeof(filename), "%s/.asterisk_history", getenv("HOME")); snprintf(filename, sizeof(filename), "%s/.asterisk_history", getenv("HOME"));

View File

@@ -12,7 +12,7 @@
* channels for your use. * channels for your use.
* *
* This program is free software, distributed under the terms of * This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file * the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree. * at the top of the source tree.
*/ */
@@ -65,6 +65,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/indications.h" #include "asterisk/indications.h"
#include "asterisk/taskprocessor.h" #include "asterisk/taskprocessor.h"
#include "asterisk/xmldoc.h" #include "asterisk/xmldoc.h"
#include "asterisk/astobj2.h"
/*! /*!
* \note I M P O R T A N T : * \note I M P O R T A N T :
@@ -933,9 +934,15 @@ struct ast_hint {
struct ast_exten *exten; /*!< Extension */ struct ast_exten *exten; /*!< Extension */
int laststate; /*!< Last known state */ int laststate; /*!< Last known state */
AST_LIST_HEAD_NOLOCK(, ast_state_cb) callbacks; /*!< Callback list for this extension */ AST_LIST_HEAD_NOLOCK(, ast_state_cb) callbacks; /*!< Callback list for this extension */
AST_RWLIST_ENTRY(ast_hint) list;/*!< Pointer to next hint in list */
}; };
/* --- Hash tables of various objects --------*/
#ifdef LOW_MEMORY
static const int HASH_EXTENHINT_SIZE = 17;
#else
static const int HASH_EXTENHINT_SIZE = 563;
#endif
static const struct cfextension_states { static const struct cfextension_states {
int extension_state; int extension_state;
const char * const text; const char * const text;
@@ -1176,13 +1183,14 @@ static AST_RWLIST_HEAD_STATIC(switches, ast_switch);
static int stateid = 1; static int stateid = 1;
/* WARNING: /* WARNING:
When holding this list's lock, do _not_ do anything that will cause conlock When holding this container's lock, do _not_ do anything that will cause conlock
to be taken, unless you _already_ hold it. The ast_merge_contexts_and_delete to be taken, unless you _already_ hold it. The ast_merge_contexts_and_delete
function will take the locks in conlock/hints order, so any other function will take the locks in conlock/hints order, so any other
paths that require both locks must also take them in that order. paths that require both locks must also take them in that order.
*/ */
static AST_RWLIST_HEAD_STATIC(hints, ast_hint); static struct ao2_container *hints;
/* XXX TODO Convert this to an astobj2 container, too. */
static AST_LIST_HEAD_NOLOCK_STATIC(statecbs, ast_state_cb); static AST_LIST_HEAD_NOLOCK_STATIC(statecbs, ast_state_cb);
#ifdef CONTEXT_DEBUG #ifdef CONTEXT_DEBUG
@@ -4224,15 +4232,15 @@ static int handle_statechange(void *datap)
struct ast_hint *hint; struct ast_hint *hint;
struct ast_str *str; struct ast_str *str;
struct statechange *sc = datap; struct statechange *sc = datap;
struct ao2_iterator i;
if (!(str = ast_str_create(1024))) { if (!(str = ast_str_create(1024))) {
return -1; return -1;
} }
ast_rdlock_contexts();
AST_RWLIST_RDLOCK(&hints);
AST_RWLIST_TRAVERSE(&hints, hint, list) { 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 *cblist; struct ast_state_cb *cblist;
char *cur, *parse; char *cur, *parse;
int state; int state;
@@ -4259,6 +4267,18 @@ static int handle_statechange(void *datap)
/* Device state changed since last check - notify the watchers */ /* Device state changed since last check - notify the watchers */
ast_rdlock_contexts();
ao2_lock(hints);
ao2_lock(hint);
if (hint->exten == NULL) {
/* the extension has been destroyed */
ao2_unlock(hint);
ao2_unlock(hints);
ast_unlock_contexts();
continue;
}
/* For general callbacks */ /* For general callbacks */
AST_LIST_TRAVERSE(&statecbs, cblist, entry) { AST_LIST_TRAVERSE(&statecbs, cblist, entry) {
cblist->callback(hint->exten->parent->name, hint->exten->exten, state, cblist->data); cblist->callback(hint->exten->parent->name, hint->exten->exten, state, cblist->data);
@@ -4270,9 +4290,11 @@ static int handle_statechange(void *datap)
} }
hint->laststate = state; /* record we saw the change */ hint->laststate = state; /* record we saw the change */
ao2_unlock(hint);
ao2_unlock(hints);
ast_unlock_contexts();
} }
AST_RWLIST_UNLOCK(&hints); ao2_iterator_destroy(&i);
ast_unlock_contexts();
ast_free(str); ast_free(str);
ast_free(sc); ast_free(sc);
return 0; return 0;
@@ -4288,19 +4310,19 @@ int ast_extension_state_add(const char *context, const char *exten,
/* If there's no context and extension: add callback to statecbs list */ /* If there's no context and extension: add callback to statecbs list */
if (!context && !exten) { if (!context && !exten) {
AST_RWLIST_WRLOCK(&hints); ao2_lock(hints);
AST_LIST_TRAVERSE(&statecbs, cblist, entry) { AST_LIST_TRAVERSE(&statecbs, cblist, entry) {
if (cblist->callback == callback) { if (cblist->callback == callback) {
cblist->data = data; cblist->data = data;
AST_RWLIST_UNLOCK(&hints); ao2_unlock(hints);
return 0; return 0;
} }
} }
/* Now insert the callback */ /* Now insert the callback */
if (!(cblist = ast_calloc(1, sizeof(*cblist)))) { if (!(cblist = ast_calloc(1, sizeof(*cblist)))) {
AST_RWLIST_UNLOCK(&hints); ao2_unlock(hints);
return -1; return -1;
} }
cblist->id = 0; cblist->id = 0;
@@ -4309,8 +4331,7 @@ int ast_extension_state_add(const char *context, const char *exten,
AST_LIST_INSERT_HEAD(&statecbs, cblist, entry); AST_LIST_INSERT_HEAD(&statecbs, cblist, entry);
AST_RWLIST_UNLOCK(&hints); ao2_unlock(hints);
return 0; return 0;
} }
@@ -4338,22 +4359,15 @@ int ast_extension_state_add(const char *context, const char *exten,
} }
/* Find the hint in the list of hints */ /* Find the hint in the list of hints */
AST_RWLIST_WRLOCK(&hints); hint = ao2_find(hints, e, 0);
AST_RWLIST_TRAVERSE(&hints, hint, list) {
if (hint->exten == e)
break;
}
if (!hint) { if (!hint) {
/* We have no hint, sorry */
AST_RWLIST_UNLOCK(&hints);
return -1; return -1;
} }
/* Now insert the callback in the callback list */ /* Now insert the callback in the callback list */
if (!(cblist = ast_calloc(1, sizeof(*cblist)))) { if (!(cblist = ast_calloc(1, sizeof(*cblist)))) {
AST_RWLIST_UNLOCK(&hints); ao2_ref(hint, -1);
return -1; return -1;
} }
@@ -4361,25 +4375,43 @@ int ast_extension_state_add(const char *context, const char *exten,
cblist->callback = callback; /* Pointer to callback routine */ cblist->callback = callback; /* Pointer to callback routine */
cblist->data = data; /* Data for the callback */ cblist->data = data; /* Data for the callback */
ao2_lock(hint);
AST_LIST_INSERT_HEAD(&hint->callbacks, cblist, entry); AST_LIST_INSERT_HEAD(&hint->callbacks, cblist, entry);
ao2_unlock(hint);
AST_RWLIST_UNLOCK(&hints); ao2_ref(hint, -1);
return cblist->id; return cblist->id;
} }
/*! \brief Remove a watcher from the callback list */ /*! \brief Remove a watcher from the callback list */
static int find_hint_by_cb_id(void *obj, void *arg, int flags)
{
const struct ast_hint *hint = obj;
int *id = arg;
struct ast_state_cb *cb;
AST_LIST_TRAVERSE(&hint->callbacks, cb, entry) {
if (cb->id == *id) {
return CMP_MATCH | CMP_STOP;
}
}
return 0;
}
/*! \brief ast_extension_state_del: Remove a watcher from the callback list */
int ast_extension_state_del(int id, ast_state_cb_type callback) int ast_extension_state_del(int id, ast_state_cb_type callback)
{ {
struct ast_state_cb *p_cur = NULL; struct ast_state_cb *p_cur = NULL;
int ret = -1; int ret = -1;
if (!id && !callback) if (!id && !callback) {
return -1; return -1;
}
AST_RWLIST_WRLOCK(&hints);
if (!id) { /* id == 0 is a callback without extension */ if (!id) { /* id == 0 is a callback without extension */
ao2_lock(hints);
AST_LIST_TRAVERSE_SAFE_BEGIN(&statecbs, p_cur, entry) { AST_LIST_TRAVERSE_SAFE_BEGIN(&statecbs, p_cur, entry) {
if (p_cur->callback == callback) { if (p_cur->callback == callback) {
AST_LIST_REMOVE_CURRENT(entry); AST_LIST_REMOVE_CURRENT(entry);
@@ -4387,19 +4419,25 @@ int ast_extension_state_del(int id, ast_state_cb_type callback)
} }
} }
AST_LIST_TRAVERSE_SAFE_END; AST_LIST_TRAVERSE_SAFE_END;
ao2_unlock(hints);
} else { /* callback with extension, find the callback based on ID */ } else { /* callback with extension, find the callback based on ID */
struct ast_hint *hint; struct ast_hint *hint;
AST_RWLIST_TRAVERSE(&hints, hint, list) {
hint = ao2_callback(hints, 0, find_hint_by_cb_id, &id);
if (hint) {
ao2_lock(hint);
AST_LIST_TRAVERSE_SAFE_BEGIN(&hint->callbacks, p_cur, entry) { AST_LIST_TRAVERSE_SAFE_BEGIN(&hint->callbacks, p_cur, entry) {
if (p_cur->id == id) { if (p_cur->id == id) {
AST_LIST_REMOVE_CURRENT(entry); AST_LIST_REMOVE_CURRENT(entry);
ret = 0;
break; break;
} }
} }
AST_LIST_TRAVERSE_SAFE_END; AST_LIST_TRAVERSE_SAFE_END;
if (p_cur) ao2_unlock(hint);
break; ao2_ref(hint, -1);
} }
} }
@@ -4407,70 +4445,61 @@ int ast_extension_state_del(int id, ast_state_cb_type callback)
ast_free(p_cur); ast_free(p_cur);
} }
AST_RWLIST_UNLOCK(&hints);
return ret; return ret;
} }
/*! \brief Add hint to hint list, check initial extension state; the hints had better be WRLOCKED already! */
static int ast_add_hint_nolock(struct ast_exten *e)
{
struct ast_hint *hint;
if (!e)
return -1;
/* Search if hint exists, do nothing */
AST_RWLIST_TRAVERSE(&hints, hint, list) {
if (hint->exten == e) {
ast_debug(2, "HINTS: Not re-adding existing hint %s: %s\n", ast_get_extension_name(e), ast_get_extension_app(e));
return -1;
}
}
ast_debug(2, "HINTS: Adding hint %s: %s\n", ast_get_extension_name(e), ast_get_extension_app(e));
if (!(hint = ast_calloc(1, sizeof(*hint)))) {
return -1;
}
/* Initialize and insert new item at the top */
hint->exten = e;
hint->laststate = ast_extension_state2(e);
AST_RWLIST_INSERT_HEAD(&hints, hint, list);
return 0;
}
/*! \brief Add hint to hint list, check initial extension state */ /*! \brief Add hint to hint list, check initial extension state */
static int ast_add_hint(struct ast_exten *e) static int ast_add_hint(struct ast_exten *e)
{ {
int ret; struct ast_hint *hint;
AST_RWLIST_WRLOCK(&hints); if (!e) {
ret = ast_add_hint_nolock(e); return -1;
AST_RWLIST_UNLOCK(&hints); }
return ret; /* 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;
}
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))) {
return -1;
}
/* Initialize and insert new item at the top */
hint->exten = e;
hint->laststate = ast_extension_state2(e);
ao2_link(hints, hint);
ao2_ref(hint, -1);
return 0;
} }
/*! \brief Change hint for an extension */ /*! \brief Change hint for an extension */
static int ast_change_hint(struct ast_exten *oe, struct ast_exten *ne) static int ast_change_hint(struct ast_exten *oe, struct ast_exten *ne)
{ {
struct ast_hint *hint; struct ast_hint *hint;
int res = -1;
AST_RWLIST_WRLOCK(&hints); hint = ao2_find(hints, oe, 0);
AST_RWLIST_TRAVERSE(&hints, hint, list) {
if (hint->exten == oe) { if (!hint) {
hint->exten = ne; return -1;
res = 0;
break;
}
} }
AST_RWLIST_UNLOCK(&hints);
return res; ao2_lock(hint);
hint->exten = ne;
ao2_unlock(hint);
ao2_ref(hint, -1);
return 0;
} }
/*! \brief Remove hint from extension */ /*! \brief Remove hint from extension */
@@ -4479,32 +4508,31 @@ static int ast_remove_hint(struct ast_exten *e)
/* Cleanup the Notifys if hint is removed */ /* Cleanup the Notifys if hint is removed */
struct ast_hint *hint; struct ast_hint *hint;
struct ast_state_cb *cblist; struct ast_state_cb *cblist;
int res = -1;
if (!e) if (!e) {
return -1; return -1;
AST_RWLIST_TRAVERSE_SAFE_BEGIN(&hints, hint, list) {
if (hint->exten != e)
continue;
while ((cblist = AST_LIST_REMOVE_HEAD(&hint->callbacks, entry))) {
/* Notify with -1 and remove all callbacks */
cblist->callback(hint->exten->parent->name, hint->exten->exten,
AST_EXTENSION_DEACTIVATED, cblist->data);
ast_free(cblist);
}
AST_RWLIST_REMOVE_CURRENT(list);
ast_free(hint);
res = 0;
break;
} }
AST_RWLIST_TRAVERSE_SAFE_END;
return res; hint = ao2_find(hints, e, 0);
if (!hint) {
return -1;
}
ao2_lock(hint);
while ((cblist = AST_LIST_REMOVE_HEAD(&hint->callbacks, entry))) {
/* Notify with -1 and remove all callbacks */
cblist->callback(hint->exten->parent->name, hint->exten->exten,
AST_EXTENSION_DEACTIVATED, cblist->data);
ast_free(cblist);
}
hint->exten = NULL;
ao2_unlink(hints, hint);
ao2_unlock(hint);
ao2_ref(hint, -1);
return 0;
} }
@@ -5721,6 +5749,7 @@ static char *handle_show_hints(struct ast_cli_entry *e, int cmd, struct ast_cli_
int num = 0; int num = 0;
int watchers; int watchers;
struct ast_state_cb *watcher; struct ast_state_cb *watcher;
struct ao2_iterator i;
switch (cmd) { switch (cmd) {
case CLI_INIT: case CLI_INIT:
@@ -5733,15 +5762,16 @@ static char *handle_show_hints(struct ast_cli_entry *e, int cmd, struct ast_cli_
return NULL; return NULL;
} }
AST_RWLIST_RDLOCK(&hints); if (ao2_container_count(hints) == 0) {
if (AST_RWLIST_EMPTY(&hints)) {
ast_cli(a->fd, "There are no registered dialplan hints\n"); ast_cli(a->fd, "There are no registered dialplan hints\n");
AST_RWLIST_UNLOCK(&hints);
return CLI_SUCCESS; return CLI_SUCCESS;
} }
/* ... we have hints ... */ /* ... we have hints ... */
ast_cli(a->fd, "\n -= Registered Asterisk Dial Plan Hints =-\n"); ast_cli(a->fd, "\n -= Registered Asterisk Dial Plan Hints =-\n");
AST_RWLIST_TRAVERSE(&hints, hint, list) {
i = ao2_iterator_init(hints, 0);
for (hint = ao2_iterator_next(&i); hint; ao2_ref(hint, -1), hint = ao2_iterator_next(&i)) {
watchers = 0; watchers = 0;
AST_LIST_TRAVERSE(&hint->callbacks, watcher, entry) { AST_LIST_TRAVERSE(&hint->callbacks, watcher, entry) {
watchers++; watchers++;
@@ -5753,9 +5783,10 @@ static char *handle_show_hints(struct ast_cli_entry *e, int cmd, struct ast_cli_
ast_extension_state2str(hint->laststate), watchers); ast_extension_state2str(hint->laststate), watchers);
num++; num++;
} }
ao2_iterator_destroy(&i);
ast_cli(a->fd, "----------------\n"); ast_cli(a->fd, "----------------\n");
ast_cli(a->fd, "- %d hints registered\n", num); ast_cli(a->fd, "- %d hints registered\n", num);
AST_RWLIST_UNLOCK(&hints);
return CLI_SUCCESS; return CLI_SUCCESS;
} }
@@ -5766,21 +5797,24 @@ static char *complete_core_show_hint(const char *line, const char *word, int pos
char *ret = NULL; char *ret = NULL;
int which = 0; int which = 0;
int wordlen; int wordlen;
struct ao2_iterator i;
if (pos != 3) if (pos != 3)
return NULL; return NULL;
wordlen = strlen(word); wordlen = strlen(word);
AST_RWLIST_RDLOCK(&hints);
/* walk through all hints */ /* walk through all hints */
AST_RWLIST_TRAVERSE(&hints, hint, list) { i = ao2_iterator_init(hints, 0);
if (!strncasecmp(word, ast_get_extension_name(hint->exten), wordlen) && ++which > state) { for (hint = ao2_iterator_next(&i); hint; ao2_unlock(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)); ret = ast_strdup(ast_get_extension_name(hint->exten));
ao2_unlock(hint);
break; break;
} }
} }
AST_RWLIST_UNLOCK(&hints); ao2_iterator_destroy(&i);
return ret; return ret;
} }
@@ -5792,6 +5826,7 @@ static char *handle_show_hint(struct ast_cli_entry *e, int cmd, struct ast_cli_a
int watchers; int watchers;
int num = 0, extenlen; int num = 0, extenlen;
struct ast_state_cb *watcher; struct ast_state_cb *watcher;
struct ao2_iterator i;
switch (cmd) { switch (cmd) {
case CLI_INIT: case CLI_INIT:
@@ -5807,15 +5842,16 @@ static char *handle_show_hint(struct ast_cli_entry *e, int cmd, struct ast_cli_a
if (a->argc < 4) if (a->argc < 4)
return CLI_SHOWUSAGE; return CLI_SHOWUSAGE;
AST_RWLIST_RDLOCK(&hints); if (ao2_container_count(hints) == 0) {
if (AST_RWLIST_EMPTY(&hints)) {
ast_cli(a->fd, "There are no registered dialplan hints\n"); ast_cli(a->fd, "There are no registered dialplan hints\n");
AST_RWLIST_UNLOCK(&hints);
return CLI_SUCCESS; return CLI_SUCCESS;
} }
extenlen = strlen(a->argv[3]); extenlen = strlen(a->argv[3]);
AST_RWLIST_TRAVERSE(&hints, hint, list) { i = ao2_iterator_init(hints, 0);
if (!strncasecmp(ast_get_extension_name(hint->exten), a->argv[3], extenlen)) { for (hint = ao2_iterator_next(&i); hint; ao2_unlock(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 = 0; watchers = 0;
AST_LIST_TRAVERSE(&hint->callbacks, watcher, entry) { AST_LIST_TRAVERSE(&hint->callbacks, watcher, entry) {
watchers++; watchers++;
@@ -5828,7 +5864,7 @@ static char *handle_show_hint(struct ast_cli_entry *e, int cmd, struct ast_cli_a
num++; num++;
} }
} }
AST_RWLIST_UNLOCK(&hints); ao2_iterator_destroy(&i);
if (!num) if (!num)
ast_cli(a->fd, "No hints matching extension %s\n", a->argv[3]); ast_cli(a->fd, "No hints matching extension %s\n", a->argv[3]);
else else
@@ -7020,6 +7056,7 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
int length; int length;
struct ast_state_cb *thiscb; struct ast_state_cb *thiscb;
struct ast_hashtab_iter *iter; struct ast_hashtab_iter *iter;
struct ao2_iterator i;
/* it is very important that this function hold the hint list lock _and_ the conlock /* it is very important that this function hold the hint list lock _and_ the conlock
during its operation; not only do we need to ensure that the list of contexts during its operation; not only do we need to ensure that the list of contexts
@@ -7040,15 +7077,24 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
} }
ast_hashtab_end_traversal(iter); ast_hashtab_end_traversal(iter);
AST_RWLIST_WRLOCK(&hints); ao2_lock(hints);
writelocktime = ast_tvnow(); writelocktime = ast_tvnow();
/* preserve all watchers for hints */ /* preserve all watchers for hints */
AST_RWLIST_TRAVERSE(&hints, hint, list) { 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 (!AST_LIST_EMPTY(&hint->callbacks)) { if (!AST_LIST_EMPTY(&hint->callbacks)) {
length = strlen(hint->exten->exten) + strlen(hint->exten->parent->name) + 2 + sizeof(*this); length = strlen(hint->exten->exten) + strlen(hint->exten->parent->name) + 2 + sizeof(*this);
if (!(this = ast_calloc(1, length))) if (!(this = ast_calloc(1, length))) {
continue; continue;
}
ao2_lock(hint);
if (hint->exten == NULL) {
ao2_unlock(hint);
continue;
}
/* this removes all the callbacks from the hint into this. */ /* this removes all the callbacks from the hint into this. */
AST_LIST_APPEND_LIST(&this->callbacks, &hint->callbacks, entry); AST_LIST_APPEND_LIST(&this->callbacks, &hint->callbacks, entry);
this->laststate = hint->laststate; this->laststate = hint->laststate;
@@ -7056,6 +7102,7 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
strcpy(this->data, hint->exten->parent->name); strcpy(this->data, hint->exten->parent->name);
this->exten = this->data + strlen(this->context) + 1; this->exten = this->data + strlen(this->context) + 1;
strcpy(this->exten, hint->exten->exten); strcpy(this->exten, hint->exten->exten);
ao2_unlock(hint);
AST_LIST_INSERT_HEAD(&store, this, list); AST_LIST_INSERT_HEAD(&store, this, list);
} }
} }
@@ -7086,10 +7133,7 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
} }
/* Find the hint in the list of hints */ /* Find the hint in the list of hints */
AST_RWLIST_TRAVERSE(&hints, hint, list) { hint = ao2_find(hints, exten, 0);
if (hint->exten == exten)
break;
}
if (!exten || !hint) { if (!exten || !hint) {
/* this hint has been removed, notify the watchers */ /* this hint has been removed, notify the watchers */
while ((thiscb = AST_LIST_REMOVE_HEAD(&this->callbacks, entry))) { while ((thiscb = AST_LIST_REMOVE_HEAD(&this->callbacks, entry))) {
@@ -7097,13 +7141,18 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
ast_free(thiscb); ast_free(thiscb);
} }
} else { } else {
ao2_lock(hint);
AST_LIST_APPEND_LIST(&hint->callbacks, &this->callbacks, entry); AST_LIST_APPEND_LIST(&hint->callbacks, &this->callbacks, entry);
hint->laststate = this->laststate; hint->laststate = this->laststate;
ao2_unlock(hint);
} }
ast_free(this); ast_free(this);
if (hint) {
ao2_ref(hint, -1);
}
} }
AST_RWLIST_UNLOCK(&hints); ao2_unlock(hints);
ast_unlock_contexts(); ast_unlock_contexts();
endlocktime = ast_tvnow(); endlocktime = ast_tvnow();
@@ -7973,7 +8022,7 @@ static int add_pri_lockopt(struct ast_context *con, struct ast_exten *tmp,
if (lockhints) { if (lockhints) {
ast_add_hint(tmp); ast_add_hint(tmp);
} else { } else {
ast_add_hint_nolock(tmp); ast_add_hint(tmp);
} }
} }
} }
@@ -8199,7 +8248,7 @@ static int ast_add_extension2_lockopt(struct ast_context *con,
if (lockhints) { if (lockhints) {
ast_add_hint(tmp); ast_add_hint(tmp);
} else { } else {
ast_add_hint_nolock(tmp); ast_add_hint(tmp);
} }
} }
} }
@@ -9773,14 +9822,14 @@ static int hints_data_provider_get(const struct ast_data_search *search,
struct ast_hint *hint; struct ast_hint *hint;
int watchers; int watchers;
struct ast_state_cb *watcher; struct ast_state_cb *watcher;
struct ao2_iterator i;
AST_RWLIST_RDLOCK(&hints); if (ao2_container_count(hints) == 0) {
if (AST_RWLIST_EMPTY(&hints)) {
AST_RWLIST_UNLOCK(&hints);
return 0; return 0;
} }
AST_RWLIST_TRAVERSE(&hints, hint, list) { i = ao2_iterator_init(hints, 0);
for (hint = ao2_iterator_next(&i); hint; ao2_ref(hint, -1), hint = ao2_iterator_next(&i)) {
watchers = 0; watchers = 0;
AST_LIST_TRAVERSE(&hint->callbacks, watcher, entry) { AST_LIST_TRAVERSE(&hint->callbacks, watcher, entry) {
watchers++; watchers++;
@@ -9799,7 +9848,7 @@ static int hints_data_provider_get(const struct ast_data_search *search,
ast_data_remove_node(data_root, data_hint); ast_data_remove_node(data_root, data_hint);
} }
} }
AST_RWLIST_UNLOCK(&hints); ao2_iterator_destroy(&i);
return 0; return 0;
} }
@@ -10169,3 +10218,31 @@ char *ast_complete_applications(const char *line, const char *word, int state)
return ret; return ret;
} }
static int hint_hash(const void *obj, const int flags)
{
const struct ast_hint *hint = obj;
int res = -1;
if (ast_get_extension_name(hint->exten)) {
res = ast_str_case_hash(ast_get_extension_name(hint->exten));
}
return res;
}
static int hint_cmp(void *obj, void *arg, int flags)
{
const struct ast_hint *hint = obj;
const struct ast_exten *exten = arg;
return (hint->exten == exten) ? CMP_MATCH | CMP_STOP : 0;
}
int ast_pbx_init(void)
{
hints = ao2_container_alloc(HASH_EXTENHINT_SIZE, hint_hash, hint_cmp);
return hints ? 0 : -1;
}