diff --git a/channels/chan_sip.c b/channels/chan_sip.c index da7a12b4a8733e91d65848fb048b1b438e014798..3b8c3ede53dc990c1d8587ae24d4877e17996f38 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -7561,6 +7561,72 @@ static void forked_invite_init(struct sip_request *req, const char *new_theirtag dialog_unref(p, "setup forked invite termination"); } +/*! \internal + * + * \brief Locks both pvt and pvt owner if owner is present. + * + * \note This function gives a ref to pvt->owner if it is present and locked. + * This reference must be decremented after pvt->owner is unlocked. + * + * \note This function will never give you up, + * \note This function will never let you down. + * \note This function will run around and desert you. + * + * \pre vpt is not locked + * \post pvt is locked + * \post pvt->owner is locked and its reference count is increased (if pvt->owner is not NULL) + * + * \returns a pointer to the locked and reffed pvt->owner channel if it exists. + */ +static struct ast_channel *sip_pvt_lock_full(struct sip_pvt *pvt) +{ + struct ast_channel *chan; + + /* Locking is simple when it is done right. If you see a deadlock resulting + * in this function, it is not this function's fault, Your problem exists elsewhere. + * This function is perfect... seriously. */ + for (;;) { + /* First, get the channel and grab a reference to it */ + sip_pvt_lock(pvt); + chan = pvt->owner; + if (chan) { + /* The channel can not go away while we hold the pvt lock. + * Give the channel a ref so it will not go away after we let + * the pvt lock go. */ + ast_channel_ref(chan); + } else { + /* no channel, return pvt locked */ + return NULL; + } + + /* We had to hold the pvt lock while getting a ref to the owner channel + * but now we have to let this lock go in order to preserve proper + * locking order when grabbing the channel lock */ + sip_pvt_unlock(pvt); + + /* Look, no deadlock avoidance, hooray! */ + ast_channel_lock(chan); + sip_pvt_lock(pvt); + + if (pvt->owner == chan) { + /* done */ + break; + } + + /* If the owner changed while everything was unlocked, no problem, + * just start over and everthing will work. This is rare, do not be + * confused by this loop and think this it is an expensive operation. + * The majority of the calls to this function will never involve multiple + * executions of this loop. */ + ast_channel_unlock(chan); + ast_channel_unref(chan); + sip_pvt_unlock(pvt); + } + + /* If owner exists, it is locked and reffed */ + return pvt->owner; +} + /*! \brief find or create a dialog structure for an incoming SIP message. * Connect incoming SIP message to current dialog or create new dialog structure * Returns a reference to the sip_pvt object, remember to give it back once done. @@ -7625,7 +7691,6 @@ static struct sip_pvt *find_call(struct sip_request *req, struct ast_sockaddr *a sip_pvt_ptr = ao2_t_find(dialogs, &tmp_dialog, OBJ_POINTER, "ao2_find in dialogs"); if (sip_pvt_ptr) { /* well, if we don't find it-- what IS in there? */ /* Found the call */ - sip_pvt_lock(sip_pvt_ptr); return sip_pvt_ptr; } } else { /* in pedantic mode! -- do the fancy search */ @@ -7676,7 +7741,6 @@ static struct sip_pvt *find_call(struct sip_request *req, struct ast_sockaddr *a switch (found) { case SIP_REQ_MATCH: - sip_pvt_lock(sip_pvt_ptr); ao2_iterator_destroy(iterator); dialog_unref(fork_pvt, "unref fork_pvt"); free_via(via); @@ -7726,23 +7790,19 @@ static struct sip_pvt *find_call(struct sip_request *req, struct ast_sockaddr *a if (intended_method == SIP_REFER) { /* We do support REFER, but not outside of a dialog yet */ transmit_response_using_temp(callid, addr, 1, intended_method, req, "603 Declined (no dialog)"); - } else { - /* Ok, time to create a new SIP dialog object, a pvt */ - if ((p = sip_alloc(callid, addr, 1, intended_method, req))) { - /* Ok, we've created a dialog, let's go and process it */ - sip_pvt_lock(p); - } else { - /* We have a memory or file/socket error (can't allocate RTP sockets or something) so we're not - getting a dialog from sip_alloc. - - Without a dialog we can't retransmit and handle ACKs and all that, but at least - send an error message. - Sorry, we apologize for the inconvienience - */ - transmit_response_using_temp(callid, addr, 1, intended_method, req, "500 Server internal error"); - ast_debug(4, "Failed allocating SIP dialog, sending 500 Server internal error and giving up\n"); - } + /* Ok, time to create a new SIP dialog object, a pvt */ + } else if (!(p = sip_alloc(callid, addr, 1, intended_method, req))) { + /* We have a memory or file/socket error (can't allocate RTP sockets or something) so we're not + getting a dialog from sip_alloc. + + Without a dialog we can't retransmit and handle ACKs and all that, but at least + send an error message. + + Sorry, we apologize for the inconvienience + */ + transmit_response_using_temp(callid, addr, 1, intended_method, req, "500 Server internal error"); + ast_debug(4, "Failed allocating SIP dialog, sending 500 Server internal error and giving up\n"); } return p; /* can be NULL */ } else if( sip_methods[intended_method].can_create == CAN_CREATE_DIALOG_UNSUPPORTED_METHOD) { @@ -24250,94 +24310,6 @@ static int handle_incoming(struct sip_pvt *p, struct sip_request *req, struct as return res; } -static void process_request_queue(struct sip_pvt *p, int *recount, int *nounlock) -{ - struct sip_request *req; - - while ((req = AST_LIST_REMOVE_HEAD(&p->request_queue, next))) { - /*! \todo XXX if nounlock is nonzero we do not have the channel lock anymore. handle_incoming() assumes that it is locked. */ - if (handle_incoming(p, req, &p->recv, recount, nounlock) == -1) { - /* Request failed */ - ast_debug(1, "SIP message could not be handled, bad request: %-70.70s\n", p->callid[0] ? p->callid : "<no callid>"); - } - ast_free(req); - } -} - -static int scheduler_process_request_queue(const void *data) -{ - struct sip_pvt *p = (struct sip_pvt *) data; - int recount = 0; - int nounlock = 0; - int lockretry; - - for (lockretry = 10; lockretry > 0; lockretry--) { - sip_pvt_lock(p); - - /* lock the owner if it has one -- we may need it */ - /* because this is deadlock-prone, we need to try and unlock if failed */ - if (!p->owner || !ast_channel_trylock(p->owner)) { - break; /* locking succeeded */ - } - - if (lockretry != 1) { - sip_pvt_unlock(p); - /* Sleep for a very short amount of time */ - usleep(1); - } - } - - if (!lockretry) { - int retry = !AST_LIST_EMPTY(&p->request_queue); - - /* we couldn't get the owner lock, which is needed to process - the queued requests, so return a non-zero value, which will - cause the scheduler to run this request again later if there - still requests to be processed - */ - sip_pvt_unlock(p); - if (!retry) { - dialog_unref(p, "The ref to a dialog passed to this sched callback is going out of scope; unref it."); - } - return retry; - }; - - process_request_queue(p, &recount, &nounlock); - p->request_queue_sched_id = -1; - - if (p->owner && !nounlock) { - ast_channel_unlock(p->owner); - } - sip_pvt_unlock(p); - - if (recount) { - ast_update_use_count(); - } - - dialog_unref(p, "The ref to a dialog passed to this sched callback is going out of scope; unref it."); - - return 0; -} - -static int queue_request(struct sip_pvt *p, const struct sip_request *req) -{ - struct sip_request *newreq; - - if (!(newreq = ast_calloc(1, sizeof(*newreq)))) { - return -1; - } - - copy_request(newreq, req); - AST_LIST_INSERT_TAIL(&p->request_queue, newreq, next); - if (p->request_queue_sched_id == -1) { - if ((p->request_queue_sched_id = ast_sched_add(sched, 10, scheduler_process_request_queue, dialog_ref(p, "Increment refcount to pass dialog pointer to sched callback"))) == -1) { - dialog_unref(p, "Decrement refcount due to sched_add failure"); - } - } - - return 0; -} - /*! \brief Read data from SIP UDP socket \note sipsock_read locks the owner channel while we are processing the SIP message \return 1 on error, 0 on success @@ -24392,9 +24364,9 @@ static int sipsock_read(int *id, int fd, short events, void *ignore) static int handle_request_do(struct sip_request *req, struct ast_sockaddr *addr) { struct sip_pvt *p; + struct ast_channel *owner_chan_ref = NULL; int recount = 0; int nounlock = 0; - int lockretry; if (sip_debug_test_addr(addr)) /* Set the debug flag early on packet level */ req->debug = 1; @@ -24418,83 +24390,45 @@ static int handle_request_do(struct sip_request *req, struct ast_sockaddr *addr) ast_str_reset(req->data); /* nulling this out is NOT a good idea here. */ return 1; } + ast_mutex_lock(&netlock); - /* Process request, with netlock held, and with usual deadlock avoidance */ - for (lockretry = 10; lockretry > 0; lockretry--) { - ast_mutex_lock(&netlock); - - /* Find the active SIP dialog or create a new one */ - p = find_call(req, addr, req->method); /* returns p locked */ - if (p == NULL) { - ast_debug(1, "Invalid SIP message - rejected , no callid, len %d\n", req->len); - ast_mutex_unlock(&netlock); - return 1; - } - - copy_socket_data(&p->socket, &req->socket); + /* Find the active SIP dialog or create a new one */ + p = find_call(req, addr, req->method); /* returns p with a reference only. _NOT_ locked*/ + if (p == NULL) { + ast_debug(1, "Invalid SIP message - rejected , no callid, len %d\n", req->len); + ast_mutex_unlock(&netlock); + return 1; + } - /* Go ahead and lock the owner if it has one -- we may need it */ - /* becaues this is deadlock-prone, we need to try and unlock if failed */ - if (!p->owner || !ast_channel_trylock(p->owner)) - break; /* locking succeeded */ + /* Lock both the pvt and the owner if owner is present. This will + * not fail. */ + owner_chan_ref = sip_pvt_lock_full(p); - if (lockretry != 1) { - sip_pvt_unlock(p); - ao2_t_ref(p, -1, "release p (from find_call) inside lockretry loop"); /* we'll look for it again, but p is dead now */ - ast_mutex_unlock(&netlock); - /* Sleep for a very short amount of time */ - usleep(1); - } - } + copy_socket_data(&p->socket, &req->socket); ast_sockaddr_copy(&p->recv, addr); if (p->do_history) /* This is a request or response, note what it was for */ append_history(p, "Rx", "%s / %s / %s", req->data->str, get_header(req, "CSeq"), REQ_OFFSET_TO_STR(req, rlPart2)); - if (!lockretry) { - if (!queue_request(p, req)) { - /* the request has been queued for later handling */ - sip_pvt_unlock(p); - ao2_t_ref(p, -1, "release p (from find_call) after queueing request"); - ast_mutex_unlock(&netlock); - return 1; - } - - if (p->owner) - ast_log(LOG_ERROR, "Channel lock for %s could not be obtained, and request was unable to be queued.\n", S_OR(p->owner->name, "- no channel name ??? - ")); - ast_log(LOG_ERROR, "SIP transaction failed: %s \n", p->callid); - if (req->method != SIP_ACK) - transmit_response(p, "503 Server error", req); /* We must respond according to RFC 3261 sec 12.2 */ - /* XXX We could add retry-after to make sure they come back */ - append_history(p, "LockFail", "Owner lock failed, transaction failed."); - sip_pvt_unlock(p); - ao2_t_ref(p, -1, "release p (from find_call) at end of lockretry"); /* p is gone after the return */ - ast_mutex_unlock(&netlock); - return 1; - } - - /* if there are queued requests on this sip_pvt, process them first, so that everything is - handled in order - */ - if (!AST_LIST_EMPTY(&p->request_queue)) { - AST_SCHED_DEL_UNREF(sched, p->request_queue_sched_id, dialog_unref(p, "when you delete the request_queue_sched_id sched, you should dec the refcount for the stored dialog ptr")); - process_request_queue(p, &recount, &nounlock); - } - - /*! \todo XXX if nounlock is nonzero we do not have the channel lock anymore. handle_incoming() assumes that it is locked. */ if (handle_incoming(p, req, addr, &recount, &nounlock) == -1) { /* Request failed */ ast_debug(1, "SIP message could not be handled, bad request: %-70.70s\n", p->callid[0] ? p->callid : "<no callid>"); } - if (recount) + if (recount) { ast_update_use_count(); + } - if (p->owner && !nounlock) + if (p->owner && !nounlock) { ast_channel_unlock(p->owner); + } + if (owner_chan_ref) { + ast_channel_unref(owner_chan_ref); + } sip_pvt_unlock(p); ao2_t_ref(p, -1, "throw away dialog ptr from find_call at end of routine"); /* p is gone after the return */ ast_mutex_unlock(&netlock); + return 1; }