Initial commit for ticket #937: Revamping of presence management to make it more efficient

Presence enhancements:
 - finer grained buddy lock object, instead of using global PJSUA-LIB's mutex
 - individual resubscription timer for buddies and also add random delay interval so that resubscriptions don't happen simultaneously (may hog processing and bandwidth).
 - in general reduced the use of global PJSUA-LIB's mutex for more efficiency
 - added last termination code in buddy info
 - use the RPID note's text for buddy's offline status rather than the default "offline" status, if available
 - resubscribe automatically on several termination causes as explained in the ticket (still untested)

General enhancements:
 - added pjsua_schedule_timer() and pjsua_cancel_timer() APIs




git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@2956 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c
index f5e0fba..773785e 100644
--- a/pjsip/src/pjsua-lib/pjsua_core.c
+++ b/pjsip/src/pjsua-lib/pjsua_core.c
@@ -2318,6 +2318,23 @@
     return p ? 0 : -1;
 }
 
+/*
+ * Schedule a timer entry. 
+ */
+PJ_DEF(pj_status_t) pjsua_schedule_timer( pj_timer_entry *entry,
+					  const pj_time_val *delay)
+{
+    return pjsip_endpt_schedule_timer(pjsua_var.endpt, entry, delay);
+}
+
+/*
+ * Cancel the previously scheduled timer.
+ *
+ */
+PJ_DEF(void) pjsua_cancel_timer(pj_timer_entry *entry)
+{
+    pjsip_endpt_cancel_timer(pjsua_var.endpt, entry);
+}
 
 /** 
  * Normalize route URI (check for ";lr" and append one if it doesn't
diff --git a/pjsip/src/pjsua-lib/pjsua_pres.c b/pjsip/src/pjsua-lib/pjsua_pres.c
index 935a41e..88a0f66 100644
--- a/pjsip/src/pjsua-lib/pjsua_pres.c
+++ b/pjsip/src/pjsua-lib/pjsua_pres.c
@@ -24,13 +24,14 @@
 #define THIS_FILE   "pjsua_pres.c"
 
 
-static void subscribe_buddy_presence(unsigned index);
+static void subscribe_buddy_presence(pjsua_buddy_id buddy_id);
+static void unsubscribe_buddy_presence(pjsua_buddy_id buddy_id);
 
 
 /*
  * Find buddy.
  */
-static pjsua_buddy_id pjsua_find_buddy(const pjsip_uri *uri)
+static pjsua_buddy_id find_buddy(const pjsip_uri *uri)
 {
     const pjsip_sip_uri *sip_uri;
     unsigned i;
@@ -60,6 +61,87 @@
     return PJSUA_INVALID_ID;
 }
 
+#define LOCK_DIALOG	1
+#define LOCK_PJSUA	2
+#define LOCK_ALL	(LOCK_DIALOG | LOCK_PJSUA)
+
+/* Buddy lock object */
+struct buddy_lock
+{
+    pjsua_buddy	    *buddy;
+    pjsip_dialog    *dlg;
+    pj_uint8_t	     flag;
+};
+
+/* Acquire lock to the specified buddy_id */
+pj_status_t lock_buddy(const char *title,
+		       pjsua_buddy_id buddy_id,
+		       struct buddy_lock *lck,
+		       unsigned _unused_)
+{
+    enum { MAX_RETRY=50 };
+    pj_bool_t has_pjsua_lock = PJ_FALSE;
+    unsigned retry;
+
+    PJ_UNUSED_ARG(_unused_);
+
+    pj_bzero(lck, sizeof(*lck));
+
+    for (retry=0; retry<MAX_RETRY; ++retry) {
+	
+	if (PJSUA_TRY_LOCK() != PJ_SUCCESS) {
+	    pj_thread_sleep(retry/10);
+	    continue;
+	}
+
+	has_pjsua_lock = PJ_TRUE;
+	lck->flag = LOCK_PJSUA;
+	lck->buddy = &pjsua_var.buddy[buddy_id];
+
+	if (lck->buddy->dlg == NULL)
+	    return PJ_SUCCESS;
+
+	if (pjsip_dlg_try_inc_lock(lck->buddy->dlg) != PJ_SUCCESS) {
+	    lck->flag = 0;
+	    lck->buddy = NULL;
+	    has_pjsua_lock = PJ_FALSE;
+	    PJSUA_UNLOCK();
+	    pj_thread_sleep(retry/10);
+	    continue;
+	}
+
+	lck->dlg = lck->buddy->dlg;
+	lck->flag = LOCK_DIALOG;
+	PJSUA_UNLOCK();
+
+	break;
+    }
+
+    if (lck->flag == 0) {
+	if (has_pjsua_lock == PJ_FALSE)
+	    PJ_LOG(1,(THIS_FILE, "Timed-out trying to acquire PJSUA mutex "
+				 "(possibly system has deadlocked) in %s",
+				 title));
+	else
+	    PJ_LOG(1,(THIS_FILE, "Timed-out trying to acquire dialog mutex "
+				 "(possibly system has deadlocked) in %s",
+				 title));
+	return PJ_ETIMEDOUT;
+    }
+    
+    return PJ_SUCCESS;
+}
+
+/* Release buddy lock */
+static void unlock_buddy(struct buddy_lock *lck)
+{
+    if (lck->flag & LOCK_DIALOG)
+	pjsip_dlg_dec_lock(lck->dlg);
+
+    if (lck->flag & LOCK_PJSUA)
+	PJSUA_UNLOCK();
+}
+
 
 /*
  * Get total number of buddies.
@@ -86,8 +168,11 @@
     uri = pjsip_parse_uri(pool, input.ptr, input.slen, 0);
     if (!uri)
 	buddy_id = PJSUA_INVALID_ID;
-    else
-	buddy_id = pjsua_find_buddy(uri);
+    else {
+	PJSUA_LOCK();
+	buddy_id = find_buddy(uri);
+	PJSUA_UNLOCK();
+    }
 
     pj_pool_release(pool);
 
@@ -139,20 +224,22 @@
 					  pjsua_buddy_info *info)
 {
     unsigned total=0;
+    struct buddy_lock lck;
     pjsua_buddy *buddy;
+    pj_status_t status;
 
-    PJ_ASSERT_RETURN(buddy_id>=0 && 
-		       buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy), 
-		     PJ_EINVAL);
-
-    PJSUA_LOCK();
+    PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id),  PJ_EINVAL);
 
     pj_bzero(info, sizeof(pjsua_buddy_info));
 
-    buddy = &pjsua_var.buddy[buddy_id];
+    status = lock_buddy("pjsua_buddy_get_info()", buddy_id, &lck, 0);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    buddy = lck.buddy;
     info->id = buddy->index;
     if (pjsua_var.buddy[buddy_id].uri.slen == 0) {
-	PJSUA_UNLOCK();
+	unlock_buddy(&lck);
 	return PJ_SUCCESS;
     }
 
@@ -186,13 +273,19 @@
 
     } else {
 	info->status = PJSUA_BUDDY_STATUS_OFFLINE;
-	info->status_text = pj_str("Offline");
+	info->rpid = buddy->status.info[0].rpid;
+
+	if (info->rpid.note.slen)
+	    info->status_text = info->rpid.note;
+	else
+	    info->status_text = pj_str("Offline");
     }
 
     /* monitor pres */
     info->monitor_pres = buddy->monitor;
 
     /* subscription state and termination reason */
+    info->sub_term_code = buddy->term_code;
     if (buddy->sub) {
 	info->sub_state = pjsip_evsub_get_state(buddy->sub);
 	info->sub_state_name = pjsip_evsub_get_state_name(buddy->sub);
@@ -218,7 +311,7 @@
 	info->sub_term_reason = pj_str("");
     }
 
-    PJSUA_UNLOCK();
+    unlock_buddy(&lck);
     return PJ_SUCCESS;
 }
 
@@ -228,15 +321,18 @@
 PJ_DEF(pj_status_t) pjsua_buddy_set_user_data( pjsua_buddy_id buddy_id,
 					       void *user_data)
 {
-    PJ_ASSERT_RETURN(buddy_id>=0 && 
-		       buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy),
-		     PJ_EINVAL);
+    struct buddy_lock lck;
+    pj_status_t status;
 
-    PJSUA_LOCK();
+    PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL);
+
+    status = lock_buddy("pjsua_buddy_set_user_data()", buddy_id, &lck, 0);
+    if (status != PJ_SUCCESS)
+	return status;
 
     pjsua_var.buddy[buddy_id].user_data = user_data;
 
-    PJSUA_UNLOCK();
+    unlock_buddy(&lck);
 
     return PJ_SUCCESS;
 }
@@ -247,17 +343,19 @@
  */
 PJ_DEF(void*) pjsua_buddy_get_user_data(pjsua_buddy_id buddy_id)
 {
+    struct buddy_lock lck;
+    pj_status_t status;
     void *user_data;
 
-    PJ_ASSERT_RETURN(buddy_id>=0 && 
-		       buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy),
-		     NULL);
+    PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), NULL);
 
-    PJSUA_LOCK();
+    status = lock_buddy("pjsua_buddy_get_user_data()", buddy_id, &lck, 0);
+    if (status != PJ_SUCCESS)
+	return NULL;
 
     user_data = pjsua_var.buddy[buddy_id].user_data;
 
-    PJSUA_UNLOCK();
+    unlock_buddy(&lck);
 
     return user_data;
 }
@@ -382,6 +480,9 @@
  */
 PJ_DEF(pj_status_t) pjsua_buddy_del(pjsua_buddy_id buddy_id)
 {
+    struct buddy_lock lck;
+    pj_status_t status;
+
     PJ_ASSERT_RETURN(buddy_id>=0 && 
 			buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy),
 		     PJ_EINVAL);
@@ -390,11 +491,13 @@
 	return PJ_SUCCESS;
     }
 
+    status = lock_buddy("pjsua_buddy_del()", buddy_id, &lck, 0);
+    if (status != PJ_SUCCESS)
+	return status;
+
     /* Unsubscribe presence */
     pjsua_buddy_subscribe_pres(buddy_id, PJ_FALSE);
 
-    PJSUA_LOCK();
-
     /* Not interested with further events for this buddy */
     if (pjsua_var.buddy[buddy_id].sub) {
 	pjsip_evsub_set_mod_data(pjsua_var.buddy[buddy_id].sub, 
@@ -408,7 +511,7 @@
     /* Reset buddy struct */
     reset_buddy(buddy_id);
 
-    PJSUA_UNLOCK();
+    unlock_buddy(&lck);
     return PJ_SUCCESS;
 }
 
@@ -419,21 +522,20 @@
 PJ_DEF(pj_status_t) pjsua_buddy_subscribe_pres( pjsua_buddy_id buddy_id,
 						pj_bool_t subscribe)
 {
-    pjsua_buddy *buddy;
+    struct buddy_lock lck;
+    pj_status_t status;
 
-    PJ_ASSERT_RETURN(buddy_id>=0 && 
-			buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy),
-		     PJ_EINVAL);
+    PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL);
 
-    PJSUA_LOCK();
+    status = lock_buddy("pjsua_buddy_subscribe_pres()", buddy_id, &lck, 0);
+    if (status != PJ_SUCCESS)
+	return status;
 
-    buddy = &pjsua_var.buddy[buddy_id];
-    buddy->monitor = subscribe;
+    lck.buddy->monitor = subscribe;
 
-    PJSUA_UNLOCK();
+    pjsua_buddy_update_pres(buddy_id);
 
-    pjsua_pres_refresh();
-
+    unlock_buddy(&lck);
     return PJ_SUCCESS;
 }
 
@@ -443,32 +545,32 @@
  */
 PJ_DEF(pj_status_t) pjsua_buddy_update_pres(pjsua_buddy_id buddy_id)
 {
-    pjsua_buddy *buddy;
+    struct buddy_lock lck;
+    pj_status_t status;
 
-    PJ_ASSERT_RETURN(buddy_id>=0 && 
-			buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy),
-		     PJ_EINVAL);
+    PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL);
 
-    PJSUA_LOCK();
+    status = lock_buddy("pjsua_buddy_update_pres()", buddy_id, &lck, 0);
+    if (status != PJ_SUCCESS)
+	return status;
 
-    buddy = &pjsua_var.buddy[buddy_id];
-
-    /* Return error if buddy's presence monitoring is not enabled */
-    if (!buddy->monitor) {
-	PJSUA_UNLOCK();
-	return PJ_EINVALIDOP;
+    /* Is this an unsubscribe request? */
+    if (!lck.buddy->monitor) {
+	unsubscribe_buddy_presence(buddy_id);
+	unlock_buddy(&lck);
+	return PJ_SUCCESS;
     }
 
     /* Ignore if presence is already active for the buddy */
-    if (buddy->sub) {
-	PJSUA_UNLOCK();
+    if (lck.buddy->sub) {
+	unlock_buddy(&lck);
 	return PJ_SUCCESS;
     }
 
     /* Initiate presence subscription */
     subscribe_buddy_presence(buddy_id);
 
-    PJSUA_UNLOCK();
+    unlock_buddy(&lck);
 
     return PJ_SUCCESS;
 }
@@ -805,7 +907,7 @@
     if (pjsua_var.ua_cfg.cb.on_incoming_subscribe) {
 	pjsua_buddy_id buddy_id;
 
-	buddy_id = pjsua_find_buddy(rdata->msg_info.from->uri);
+	buddy_id = find_buddy(rdata->msg_info.from->uri);
 
 	(*pjsua_var.ua_cfg.cb.on_incoming_subscribe)(acc_id, uapres, buddy_id,
 						     &dlg->remote.info_str, 
@@ -951,7 +1053,7 @@
 
 
     /* Subscribe to buddy's presence if we're not subscribed */
-    buddy_id = pjsua_find_buddy(srv_pres->dlg->remote.info->uri);
+    buddy_id = find_buddy(srv_pres->dlg->remote.info->uri);
     if (buddy_id != PJSUA_INVALID_ID) {
 	pjsua_buddy *b = &pjsua_var.buddy[buddy_id];
 	if (b->monitor && b->sub == NULL) {
@@ -1280,6 +1382,40 @@
  * Client subscription.
  */
 
+static void buddy_timer_cb(pj_timer_heap_t *th, pj_timer_entry *entry)
+{
+    pjsua_buddy *buddy = (pjsua_buddy*)entry->user_data;
+
+    PJ_UNUSED_ARG(th);
+
+    entry->id = PJ_FALSE;
+    pjsua_buddy_update_pres(buddy->index);
+}
+
+/* Reschedule subscription refresh timer or terminate the subscription
+ * refresh timer for the specified buddy.
+ */
+static void buddy_resubscribe(pjsua_buddy *buddy, pj_bool_t resched,
+			      unsigned msec_interval)
+{
+    if (buddy->timer.id) {
+	pjsua_cancel_timer(&buddy->timer);
+	buddy->timer.id = PJ_FALSE;
+    }
+
+    if (resched) {
+	pj_time_val delay;
+
+	pj_timer_entry_init(&buddy->timer, 0, buddy, &buddy_timer_cb);
+	delay.sec = 0;
+	delay.msec = msec_interval;
+	pj_time_val_normalize(&delay);
+
+	if (pjsua_schedule_timer(&buddy->timer, &delay)==PJ_SUCCESS)
+	    buddy->timer.id = PJ_TRUE;
+    }
+}
+
 /* Callback called when *client* subscription state has changed. */
 static void pjsua_evsub_on_state( pjsip_evsub *sub, pjsip_event *event)
 {
@@ -1287,8 +1423,10 @@
 
     PJ_UNUSED_ARG(event);
 
-    PJSUA_LOCK();
-
+    /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has
+     *   a dialog attached to it, lock_buddy() will use the dialog
+     *   lock, which we are currently holding!
+     */
     buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
     if (buddy) {
 	PJ_LOG(4,(THIS_FILE, 
@@ -1298,6 +1436,8 @@
 		  pjsip_evsub_get_state_name(sub)));
 
 	if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+	    int resub_delay = -1;
+
 	    if (buddy->term_reason.ptr == NULL) {
 		buddy->term_reason.ptr = (char*) 
 					 pj_pool_alloc(buddy->pool,
@@ -1306,7 +1446,87 @@
 	    pj_strncpy(&buddy->term_reason, 
 		       pjsip_evsub_get_termination_reason(sub), 
 		       PJSUA_BUDDY_SUB_TERM_REASON_LEN);
+
+	    buddy->term_code = 200;
+
+	    /* Determine whether to resubscribe automatically */
+	    if (event->type==PJSIP_EVENT_TSX_STATE) {
+		const pjsip_transaction *tsx = event->body.tsx_state.tsx;
+		if (pjsip_method_cmp(&tsx->method, 
+				     &pjsip_subscribe_method)==0)
+		{
+		    buddy->term_code = tsx->status_code;
+		    switch (tsx->status_code) {
+		    case PJSIP_SC_CALL_TSX_DOES_NOT_EXIST:
+			/* 481: we refreshed too late? resubscribe
+			 * immediately.
+			 */
+			resub_delay = 500;
+			break;
+		    }
+		} else if (pjsip_method_cmp(&tsx->method,
+					    &pjsip_notify_method)==0)
+		{
+		    if (pj_stricmp2(&buddy->term_reason, "deactivated")==0 ||
+			pj_stricmp2(&buddy->term_reason, "timeout")==0) {
+			/* deactivated: The subscription has been terminated, 
+			 * but the subscriber SHOULD retry immediately with 
+			 * a new subscription.
+			 */
+			/* timeout: The subscription has been terminated 
+			 * because it was not refreshed before it expired.
+			 * Clients MAY re-subscribe immediately. The 
+			 * "retry-after" parameter has no semantics for 
+			 * "timeout".
+			 */
+			resub_delay = 500;
+		    } 
+		    else if (pj_stricmp2(&buddy->term_reason, "probation")==0||
+			     pj_stricmp2(&buddy->term_reason, "giveup")==0) {
+			/* probation: The subscription has been terminated, 
+			 * but the client SHOULD retry at some later time.  
+			 * If a "retry-after" parameter is also present, the 
+			 * client SHOULD wait at least the number of seconds 
+			 * specified by that parameter before attempting to re-
+			 * subscribe.
+			 */
+			/* giveup: The subscription has been terminated because
+			 * the notifier could not obtain authorization in a 
+			 * timely fashion.  If a "retry-after" parameter is 
+			 * also present, the client SHOULD wait at least the
+			 * number of seconds specified by that parameter before
+			 * attempting to re-subscribe; otherwise, the client 
+			 * MAY retry immediately, but will likely get put back
+			 * into pending state.
+			 */
+			const pjsip_sub_state_hdr *sub_hdr;
+			pj_str_t sub_state = { "Subscription-State", 18 };
+			const pjsip_msg *msg;
+
+			msg = event->body.tsx_state.src.rdata->msg_info.msg;
+			sub_hdr = (const pjsip_sub_state_hdr*)
+				  pjsip_msg_find_hdr_by_name(msg, &sub_state,
+							     NULL);
+			if (sub_hdr && sub_hdr->retry_after > 0)
+			    resub_delay = sub_hdr->retry_after * 1000;
+		    }
+
+		}
+	    }
+
+	    /* For other cases of subscription termination, if resubscribe
+	     * timer is not set, schedule with default expiration (plus minus
+	     * some random value, to avoid sending SUBSCRIBEs all at once)
+	     */
+	    if (resub_delay == -1) {
+		pj_assert(PJSUA_PRES_TIMER >= 3);
+		resub_delay = PJSUA_PRES_TIMER*1000 - 2500 + (pj_rand()%5000);
+	    }
+
+	    buddy_resubscribe(buddy, PJ_TRUE, resub_delay);
+				  
 	} else {
+	    buddy->term_code = 0;
 	    buddy->term_reason.slen = 0;
 	}
 
@@ -1318,11 +1538,10 @@
 	if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
 	    buddy->sub = NULL;
 	    buddy->status.info_cnt = 0;
+	    buddy->dlg = NULL;
 	    pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
 	}
     }
-
-    PJSUA_UNLOCK();
 }
 
 
@@ -1334,11 +1553,12 @@
     pjsua_buddy *buddy;
     pjsip_contact_hdr *contact_hdr;
 
-    PJSUA_LOCK();
-
+    /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has
+     *   a dialog attached to it, lock_buddy() will use the dialog
+     *   lock, which we are currently holding!
+     */
     buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
     if (!buddy) {
-	PJSUA_UNLOCK();
 	return;
     }
 
@@ -1347,7 +1567,6 @@
      */
     if (buddy->contact.slen != 0) {
 	/* Contact already set */
-	PJSUA_UNLOCK();
 	return;
     }
     
@@ -1357,7 +1576,6 @@
 	event->type != PJSIP_EVENT_RX_MSG || 
 	pjsip_method_cmp(&tsx->method, pjsip_get_subscribe_method())!=0)
     {
-	PJSUA_UNLOCK();
 	return;
     }
 
@@ -1366,7 +1584,6 @@
 		  pjsip_msg_find_hdr(event->body.rx_msg.rdata->msg_info.msg,
 				     PJSIP_H_CONTACT, NULL);
     if (!contact_hdr) {
-	PJSUA_UNLOCK();
 	return;
     }
 
@@ -1378,8 +1595,6 @@
 					   PJSIP_MAX_URL_SIZE);
     if (buddy->contact.slen < 0)
 	buddy->contact.slen = 0;
-
-    PJSUA_UNLOCK();
 }
 
 
@@ -1393,8 +1608,10 @@
 {
     pjsua_buddy *buddy;
 
-    PJSUA_LOCK();
-
+    /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has
+     *   a dialog attached to it, lock_buddy() will use the dialog
+     *   lock, which we are currently holding!
+     */
     buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
     if (buddy) {
 	/* Update our info. */
@@ -1409,8 +1626,6 @@
     PJ_UNUSED_ARG(p_st_text);
     PJ_UNUSED_ARG(res_hdr);
     PJ_UNUSED_ARG(p_body);
-
-    PJSUA_UNLOCK();
 }
 
 
@@ -1436,7 +1651,7 @@
 
 
 /* It does what it says.. */
-static void subscribe_buddy_presence(unsigned index)
+static void subscribe_buddy_presence(pjsua_buddy_id buddy_id)
 {
     pj_pool_t *tmp_pool = NULL;
     pjsua_buddy *buddy;
@@ -1446,13 +1661,13 @@
     pjsip_tx_data *tdata;
     pj_status_t status;
 
-    buddy = &pjsua_var.buddy[index];
+    buddy = &pjsua_var.buddy[buddy_id];
     acc_id = pjsua_acc_find_for_outgoing(&buddy->uri);
 
     acc = &pjsua_var.acc[acc_id];
 
     PJ_LOG(4,(THIS_FILE, "Using account %d for buddy %d subscription",
-			 acc_id, index));
+			 acc_id, buddy_id));
 
     /* Generate suitable Contact header unless one is already set in
      * the account
@@ -1493,7 +1708,7 @@
     status = pjsip_pres_create_uac( buddy->dlg, &pres_callback, 
 				    PJSIP_EVSUB_NO_EVENT_ID, &buddy->sub);
     if (status != PJ_SUCCESS) {
-	pjsua_var.buddy[index].sub = NULL;
+	buddy->sub = NULL;
 	pjsua_perror(THIS_FILE, "Unable to create presence client", 
 		     status);
 	/* This should destroy the dialog since there's no session
@@ -1564,19 +1779,19 @@
 
 
 /* It does what it says... */
-static void unsubscribe_buddy_presence(unsigned index)
+static void unsubscribe_buddy_presence(pjsua_buddy_id buddy_id)
 {
     pjsua_buddy *buddy;
     pjsip_tx_data *tdata;
     pj_status_t status;
 
-    buddy = &pjsua_var.buddy[index];
+    buddy = &pjsua_var.buddy[buddy_id];
 
     if (buddy->sub == NULL)
 	return;
 
     if (pjsip_evsub_get_state(buddy->sub) == PJSIP_EVSUB_STATE_TERMINATED) {
-	pjsua_var.buddy[index].sub = NULL;
+	buddy->sub = NULL;
 	return;
     }
 
@@ -1594,39 +1809,22 @@
     }
 }
 
-
-/* Lock all buddies */
-#define LOCK_BUDDIES	unsigned cnt_ = 0; \
-			pjsip_dialog *dlg_list_[PJSUA_MAX_BUDDIES]; \
-			unsigned i_; \
-			for (i_=0; i_<PJ_ARRAY_SIZE(pjsua_var.buddy);++i_) { \
-			    if (pjsua_var.buddy[i_].sub) { \
-				dlg_list_[cnt_++] = pjsua_var.buddy[i_].dlg; \
-				pjsip_dlg_inc_lock(pjsua_var.buddy[i_].dlg); \
-			    } \
-			} \
-			PJSUA_LOCK();
-
-/* Unlock all buddies */
-#define UNLOCK_BUDDIES	PJSUA_UNLOCK(); \
-			for (i_=0; i_<cnt_; ++i_) { \
-			    pjsip_dlg_dec_lock(dlg_list_[i_]); \
-			}
-			
-
-
 /* It does what it says.. */
-static void refresh_client_subscriptions(void)
+static pj_status_t refresh_client_subscriptions(void)
 {
     unsigned i;
-
-    LOCK_BUDDIES;
+    pj_status_t status;
 
     for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
+	struct buddy_lock lck;
 
-	if (!pjsua_var.buddy[i].uri.slen)
+	if (!pjsua_buddy_is_valid(i))
 	    continue;
 
+	status = lock_buddy("refresh_client_subscriptions()", i, &lck, 0);
+	if (status != PJ_SUCCESS)
+	    return status;
+
 	if (pjsua_var.buddy[i].monitor && !pjsua_var.buddy[i].sub) {
 	    subscribe_buddy_presence(i);
 
@@ -1634,9 +1832,11 @@
 	    unsubscribe_buddy_presence(i);
 
 	}
+
+	unlock_buddy(&lck);
     }
 
-    UNLOCK_BUDDIES;
+    return PJ_SUCCESS;
 }
 
 /* Timer callback to re-create client subscription */
@@ -1654,7 +1854,11 @@
     }
 
     entry->id = PJ_FALSE;
-    refresh_client_subscriptions();
+
+    /* #937: No need to do bulk client refresh, as buddies have their
+     *       own individual timer now.
+     */
+    //refresh_client_subscriptions();
 
     pjsip_endpt_schedule_timer(pjsua_var.endpt, entry, &delay);
     entry->id = PJ_TRUE;
@@ -1707,22 +1911,6 @@
 
 
 /*
- * Refresh presence subscriptions
- */
-void pjsua_pres_refresh()
-{
-    unsigned i;
-
-    refresh_client_subscriptions();
-
-    for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
-	if (pjsua_var.acc[i].valid)
-	    pjsua_pres_update_acc(i, PJ_FALSE);
-    }
-}
-
-
-/*
  * Shutdown presence.
  */
 void pjsua_pres_shutdown(void)
@@ -1746,5 +1934,10 @@
 	pjsua_var.buddy[i].monitor = 0;
     }
 
-    pjsua_pres_refresh();
+    refresh_client_subscriptions();
+
+    for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+	if (pjsua_var.acc[i].valid)
+	    pjsua_pres_update_acc(i, PJ_FALSE);
+    }
 }