Index: channels/chan_sip.c
===================================================================
--- channels/chan_sip.c	(revision 281083)
+++ channels/chan_sip.c	(working copy)
@@ -149,6 +149,7 @@
 #include "asterisk/compiler.h"
 #include "asterisk/threadstorage.h"
 #include "asterisk/translate.h"
+#include "asterisk/astobj2.h"
 
 #ifndef FALSE
 #define FALSE    0
@@ -855,6 +856,12 @@
 #define sipdebug_config		ast_test_flag(&global_flags[1], SIP_PAGE2_DEBUG_CONFIG)
 #define sipdebug_console	ast_test_flag(&global_flags[1], SIP_PAGE2_DEBUG_CONSOLE)
 
+/*! \brief provisional keep alive scheduler item data */
+struct provisional_keepalive_data {
+	struct sip_pvt *pvt;
+	int sched_id;
+};
+
 /*! \brief T38 States for a call */
 enum t38state {
         T38_DISABLED = 0,                /*!< Not enabled */
@@ -1044,7 +1051,7 @@
 	struct ast_variable *chanvars;		/*!< Channel variables to set for inbound call */
 	AST_LIST_HEAD_NOLOCK(request_queue, sip_request) request_queue; /*!< Requests that arrived but could not be processed immediately */
 	int request_queue_sched_id;		/*!< Scheduler ID of any scheduled action to process queued requests */
-	int provisional_keepalive_sched_id; /*!< Scheduler ID for provisional responses that need to be sent out to avoid cancellation */
+	struct provisional_keepalive_data *provisional_keepalive_data; /*!< Scheduler data for provisional responses that need to be sent out to avoid cancellation */
 	const char *last_provisional;   /*!< The last successfully transmitted provisonal response message */
 	struct sip_pvt *next;			/*!< Next dialog in chain */
 	struct sip_invite_param *options;	/*!< Options for INVITE */
@@ -2335,44 +2342,121 @@
 	}
 }
 
-static int send_provisional_keepalive_full(struct sip_pvt *pvt, int with_sdp)
+/*! \brief This is called by the scheduler to resend the last provisional message in a dialog */
+static int send_provisional_keepalive_full(struct provisional_keepalive_data *data, int with_sdp)
 {
 	const char *msg = NULL;
+	int res = 0;
+	struct sip_pvt *pvt = data->pvt;
 
-	if (!pvt->last_provisional || !strncasecmp(pvt->last_provisional, "100", 3)) {
-		msg = "183 Session Progress";
+	if (!pvt) {
+		ao2_ref(data, -1); /* not rescheduling so drop ref. in this case the dialog has already dropped this ref */
+		return res;
 	}
 
-	if (pvt->invitestate < INV_COMPLETED) {
-		if (with_sdp) {
-			transmit_response_with_sdp(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq, XMIT_UNRELIABLE);
+	ast_mutex_lock(&pvt->lock);
+	while (pvt->owner && ast_channel_trylock(pvt->owner)) {
+		ast_mutex_unlock(&pvt->lock);
+		usleep(1);
+		if ((pvt = data->pvt)) {
+			ast_mutex_lock(&pvt->lock);
 		} else {
-			transmit_response(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq);
+			ao2_ref(data, -1);
+			return res;
 		}
-		return PROVIS_KEEPALIVE_TIMEOUT;
 	}
 
-	return 0;
+	if (data->sched_id == -1 || pvt->invitestate >= INV_COMPLETED) {
+		goto provisional_keepalive_cleanup;
+	}
+
+	if (!pvt->last_provisional || !strncasecmp(pvt->last_provisional, "100", 3)) {
+		msg = "183 Session Progress";
+	}
+
+	if (with_sdp) {
+		transmit_response_with_sdp(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq, XMIT_UNRELIABLE);
+	} else {
+		transmit_response(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq);
+	}
+
+	res = PROVIS_KEEPALIVE_TIMEOUT; /* reschedule the keepalive event */
+
+provisional_keepalive_cleanup:
+	if (!res) { /* not rescheduling, so drop ref */
+		data->sched_id = -1; /* if we don't re-schedule, make sure to remove the sched id */
+		ao2_ref(data, -1); /* release the scheduler's reference to this data */
+	}
+
+	if (pvt->owner) {
+		ast_channel_unlock(pvt->owner);
+	}
+	ast_mutex_unlock(&pvt->lock);
+
+	return res;
 }
 
 static int send_provisional_keepalive(const void *data) {
-	struct sip_pvt *pvt = (struct sip_pvt *) data;
+	struct provisional_keepalive_data *d = (struct provisional_keepalive_data *) data;
 
-	return send_provisional_keepalive_full(pvt, 0);
+	return send_provisional_keepalive_full(d, 0);
 }
 
 static int send_provisional_keepalive_with_sdp(const void *data) {
-	struct sip_pvt *pvt = (void *)data;
+	struct provisional_keepalive_data *d = (struct provisional_keepalive_data *) data;
 
-	return send_provisional_keepalive_full(pvt, 1);
+	return send_provisional_keepalive_full(d, 1);
 }
 
+static void *unref_provisional_keepalive(struct provisional_keepalive_data *data)
+{
+	if (data) {
+		data->sched_id = -1;
+		data->pvt = NULL;
+		ao2_ref(data, -1);
+	}
+	return NULL;
+}
+
+static void remove_provisional_keepalive_sched(struct sip_pvt *pvt)
+{
+	if (pvt->provisional_keepalive_data) {
+		int res;
+		res = AST_SCHED_DEL(sched, pvt->provisional_keepalive_data->sched_id);
+		/* If we could not remove this item. remove pvt's reference this data and mark it for removal
+		 * for the next time the scheduler uses it. The scheduler has it's own ref to this data
+		 * and will detect it should not reschedule the event since the sched_id is -1 and pvt == NULL */
+		if (res == -1) {
+			pvt->provisional_keepalive_data = unref_provisional_keepalive(pvt->provisional_keepalive_data);
+		}
+	}
+}
+
 static void update_provisional_keepalive(struct sip_pvt *pvt, int with_sdp)
 {
-	AST_SCHED_DEL(sched, pvt->provisional_keepalive_sched_id);
+	remove_provisional_keepalive_sched(pvt);
 
-	pvt->provisional_keepalive_sched_id = ast_sched_add(sched, PROVIS_KEEPALIVE_TIMEOUT,
-		with_sdp ? send_provisional_keepalive_with_sdp : send_provisional_keepalive, pvt);
+	if (!pvt->provisional_keepalive_data) {
+		if (!(pvt->provisional_keepalive_data = ao2_alloc(sizeof(*pvt->provisional_keepalive_data), NULL))) {
+			return; /* alloc error, can not recover */
+		}
+		pvt->provisional_keepalive_data->sched_id = -1;
+		pvt->provisional_keepalive_data->pvt = pvt;
+	}
+
+	/* give the scheduler a ref */
+	ao2_ref(pvt->provisional_keepalive_data, +1);
+
+	/* schedule the provisional keepalive */
+	pvt->provisional_keepalive_data->sched_id = ast_sched_add(sched,
+		PROVIS_KEEPALIVE_TIMEOUT,
+		with_sdp ? send_provisional_keepalive_with_sdp : send_provisional_keepalive,
+		pvt->provisional_keepalive_data);
+
+	/* if schedule was unsuccessful, remove the scheduler's ref */
+	if (pvt->provisional_keepalive_data->sched_id == -1) {
+		ao2_ref(pvt->provisional_keepalive_data, -1);
+	}
 }
 
 /*! \brief Transmit response on SIP request*/
@@ -2399,7 +2483,7 @@
 
 	/* If we are sending a final response to an INVITE, stop retransmitting provisional responses */
 	if (p->initreq.method == SIP_INVITE && reliable == XMIT_CRITICAL) {
-		AST_SCHED_DEL(sched, p->provisional_keepalive_sched_id);
+		remove_provisional_keepalive_sched(p);
 	}
 
 	res = (reliable) ?
@@ -3297,8 +3381,14 @@
 	AST_SCHED_DEL(sched, p->waitid);
 	AST_SCHED_DEL(sched, p->autokillid);
 	AST_SCHED_DEL(sched, p->request_queue_sched_id);
-	AST_SCHED_DEL(sched, p->provisional_keepalive_sched_id);
 
+	remove_provisional_keepalive_sched(p);
+	if (p->provisional_keepalive_data) {
+		ast_mutex_lock(&p->lock);
+		p->provisional_keepalive_data = unref_provisional_keepalive(p->provisional_keepalive_data);
+		ast_mutex_unlock(&p->lock);
+	}
+
 	if (p->rtp) {
 		ast_rtp_destroy(p->rtp);
 	}
@@ -3783,7 +3873,7 @@
 				}
 			} else {	/* Incoming call, not up */
 				const char *res;
-				AST_SCHED_DEL(sched, p->provisional_keepalive_sched_id);
+				remove_provisional_keepalive_sched(p);
 				if (p->hangupcause && (res = hangup_cause2sip(p->hangupcause)))
 					transmit_response_reliable(p, res, &p->initreq);
 				else 
@@ -4681,7 +4771,6 @@
 	p->waitid = -1;
 	p->autokillid = -1;
 	p->request_queue_sched_id = -1;
-	p->provisional_keepalive_sched_id = -1;
 	p->subscribed = NONE;
 	p->stateid = -1;
 	p->prefs = default_prefs;		/* Set default codecs for this call */
