Index: channels/chan_sip.c =================================================================== --- channels/chan_sip.c (revision 298094) +++ channels/chan_sip.c (working copy) @@ -12979,7 +12979,16 @@ { struct sip_pvt *p = data; - sip_pvt_lock(p); + if (sip_pvt_trylock(p)) { + /* This can deadlock when handle_statechange is active and we + * also use ast_get_hint with the pvt locked, because lock ordering + * differs, but cannot be changed. + * + * This error return gets handle_statechange to back off and try + * again. + */ + return -1; + } switch(state) { case AST_EXTENSION_DEACTIVATED: /* Retry after a while */ Index: main/pbx.c =================================================================== --- main/pbx.c (revision 298094) +++ main/pbx.c (working copy) @@ -3922,12 +3922,30 @@ /* For general callbacks */ AST_LIST_TRAVERSE(&statecbs, cblist, entry) { - cblist->callback(hint->exten->parent->name, hint->exten->exten, state, cblist->data); + while (cblist->callback(hint->exten->parent->name, hint->exten->exten, state, cblist->data) == -1) { + /* -1 means callback failed to get a lock, so unlock everything and retry */ + ao2_unlock(hint); + ao2_unlock(hints); + ast_unlock_contexts(); + sched_yield(); + ast_rdlock_contexts(); + ao2_lock(hints); + ao2_lock(hint); + } } /* For extension callbacks */ AST_LIST_TRAVERSE(&hint->callbacks, cblist, entry) { - cblist->callback(hint->exten->parent->name, hint->exten->exten, state, cblist->data); + while (cblist->callback(hint->exten->parent->name, hint->exten->exten, state, cblist->data) == -1) { + /* -1 means callback failed to get a lock, so unlock everything and retry */ + ao2_unlock(hint); + ao2_unlock(hints); + ast_unlock_contexts(); + sched_yield(); + ast_rdlock_contexts(); + ao2_lock(hints); + ao2_lock(hint); + } } hint->laststate = state; /* record we saw the change */ @@ -4161,10 +4179,21 @@ } ao2_lock(hint); + if (hint->exten == NULL) { + /* the extension has already been destroyed */ + ao2_unlock(hint); + ao2_ref(hint, -1); + return 0; + } + 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); + while (cblist->callback(hint->exten->parent->name, hint->exten->exten, AST_EXTENSION_DEACTIVATED, cblist->data) == -1) { + /* -1 means callback failed to get a lock, so unlock everything and retry */ + ast_unlock_contexts(); + sched_yield(); + ast_wrlock_contexts(); /* may have only been read locked originally by ast_context_remove_extension_callerid, but others write lock */ + } ast_free(cblist); } @@ -6776,7 +6805,14 @@ 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); + while (thiscb->callback(this->context, this->exten, AST_EXTENSION_REMOVED, thiscb->data) == -1) { + /* -1 means callback failed to get a lock, so unlock everything and retry */ + ao2_unlock(hints); + ast_unlock_contexts(); + sched_yield(); + ast_rdlock_contexts(); + ao2_lock(hints); + } ast_free(thiscb); } } else {