Index: pjproject/trunk/pjlib/src/pj/lock.c
===================================================================
--- pjproject/trunk/pjlib/src/pj/lock.c	(revision 3553)
+++ pjproject/trunk/pjlib/src/pj/lock.c	(revision 4359)
@@ -21,8 +21,10 @@
 #include <pj/os.h>
 #include <pj/assert.h>
+#include <pj/log.h>
 #include <pj/pool.h>
 #include <pj/string.h>
 #include <pj/errno.h>
 
+#define THIS_FILE	"lock.c"
 
 typedef void LOCK_OBJ;
@@ -197,2 +199,515 @@
 }
 
+
+/******************************************************************************
+ * Group lock
+ */
+
+/* Individual lock in the group lock */
+typedef struct grp_lock_item
+{
+    PJ_DECL_LIST_MEMBER(struct grp_lock_item);
+    int		 prio;
+    pj_lock_t	*lock;
+
+} grp_lock_item;
+
+/* Destroy callbacks */
+typedef struct grp_destroy_callback
+{
+    PJ_DECL_LIST_MEMBER(struct grp_destroy_callback);
+    void	*comp;
+    void	(*handler)(void*);
+} grp_destroy_callback;
+
+#if PJ_GRP_LOCK_DEBUG
+/* Store each add_ref caller */
+typedef struct grp_lock_ref
+{
+    PJ_DECL_LIST_MEMBER(struct grp_lock_ref);
+    const char	*file;
+    int		 line;
+} grp_lock_ref;
+#endif
+
+/* The group lock */
+struct pj_grp_lock_t
+{
+    pj_lock_t	 	 base;
+
+    pj_pool_t		*pool;
+    pj_atomic_t		*ref_cnt;
+    pj_lock_t		*own_lock;
+
+    pj_thread_t		*owner;
+    int			 owner_cnt;
+
+    grp_lock_item	 lock_list;
+    grp_destroy_callback destroy_list;
+
+#if PJ_GRP_LOCK_DEBUG
+    grp_lock_ref	 ref_list;
+    grp_lock_ref	 ref_free_list;
+#endif
+};
+
+
+PJ_DEF(void) pj_grp_lock_config_default(pj_grp_lock_config *cfg)
+{
+    pj_bzero(cfg, sizeof(*cfg));
+}
+
+static void grp_lock_set_owner_thread(pj_grp_lock_t *glock)
+{
+    if (!glock->owner) {
+	glock->owner = pj_thread_this();
+	glock->owner_cnt = 1;
+    } else {
+	pj_assert(glock->owner == pj_thread_this());
+	glock->owner_cnt++;
+    }
+}
+
+static void grp_lock_unset_owner_thread(pj_grp_lock_t *glock)
+{
+    pj_assert(glock->owner == pj_thread_this());
+    pj_assert(glock->owner_cnt > 0);
+    if (--glock->owner_cnt <= 0) {
+	glock->owner = NULL;
+	glock->owner_cnt = 0;
+    }
+}
+
+static pj_status_t grp_lock_acquire(LOCK_OBJ *p)
+{
+    pj_grp_lock_t *glock = (pj_grp_lock_t*)p;
+    grp_lock_item *lck;
+
+    pj_assert(pj_atomic_get(glock->ref_cnt) > 0);
+
+    lck = glock->lock_list.next;
+    while (lck != &glock->lock_list) {
+	pj_lock_acquire(lck->lock);
+	lck = lck->next;
+    }
+    grp_lock_set_owner_thread(glock);
+    pj_grp_lock_add_ref(glock);
+    return PJ_SUCCESS;
+}
+
+static pj_status_t grp_lock_tryacquire(LOCK_OBJ *p)
+{
+    pj_grp_lock_t *glock = (pj_grp_lock_t*)p;
+    grp_lock_item *lck;
+
+    pj_assert(pj_atomic_get(glock->ref_cnt) > 0);
+
+    lck = glock->lock_list.next;
+    while (lck != &glock->lock_list) {
+	pj_status_t status = pj_lock_tryacquire(lck->lock);
+	if (status != PJ_SUCCESS) {
+	    lck = lck->prev;
+	    while (lck != &glock->lock_list) {
+		pj_lock_release(lck->lock);
+		lck = lck->prev;
+	    }
+	    return status;
+	}
+	lck = lck->next;
+    }
+    grp_lock_set_owner_thread(glock);
+    pj_grp_lock_add_ref(glock);
+    return PJ_SUCCESS;
+}
+
+static pj_status_t grp_lock_release(LOCK_OBJ *p)
+{
+    pj_grp_lock_t *glock = (pj_grp_lock_t*)p;
+    grp_lock_item *lck;
+
+    grp_lock_unset_owner_thread(glock);
+
+    lck = glock->lock_list.prev;
+    while (lck != &glock->lock_list) {
+	pj_lock_release(lck->lock);
+	lck = lck->prev;
+    }
+    return pj_grp_lock_dec_ref(glock);
+}
+
+static pj_status_t grp_lock_destroy(LOCK_OBJ *p)
+{
+    pj_grp_lock_t *glock = (pj_grp_lock_t*)p;
+    pj_pool_t *pool = glock->pool;
+    grp_lock_item *lck;
+    grp_destroy_callback *cb;
+
+    if (!glock->pool) {
+	/* already destroyed?! */
+	return PJ_EINVAL;
+    }
+
+    /* Release all chained locks */
+    lck = glock->lock_list.next;
+    while (lck != &glock->lock_list) {
+	if (lck->lock != glock->own_lock) {
+	    unsigned i;
+	    for (i=0; i<glock->owner_cnt; ++i)
+		pj_lock_release(lck->lock);
+	}
+	lck = lck->next;
+    }
+
+    /* Call callbacks */
+    cb = glock->destroy_list.next;
+    while (cb != &glock->destroy_list) {
+	grp_destroy_callback *next = cb->next;
+	cb->handler(cb->comp);
+	cb = next;
+    }
+
+    pj_lock_destroy(glock->own_lock);
+    pj_atomic_destroy(glock->ref_cnt);
+    glock->pool = NULL;
+    pj_pool_release(pool);
+
+    return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pj_grp_lock_create( pj_pool_t *pool,
+                                        const pj_grp_lock_config *cfg,
+                                        pj_grp_lock_t **p_grp_lock)
+{
+    pj_grp_lock_t *glock;
+    grp_lock_item *own_lock;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(pool && p_grp_lock, PJ_EINVAL);
+
+    PJ_UNUSED_ARG(cfg);
+
+    pool = pj_pool_create(pool->factory, "glck%p", 512, 512, NULL);
+    if (!pool)
+	return PJ_ENOMEM;
+
+    glock = PJ_POOL_ZALLOC_T(pool, pj_grp_lock_t);
+    glock->base.lock_object = glock;
+    glock->base.acquire = &grp_lock_acquire;
+    glock->base.tryacquire = &grp_lock_tryacquire;
+    glock->base.release = &grp_lock_release;
+    glock->base.destroy = &grp_lock_destroy;
+
+    glock->pool = pool;
+    pj_list_init(&glock->lock_list);
+    pj_list_init(&glock->destroy_list);
+#if PJ_GRP_LOCK_DEBUG
+    pj_list_init(&glock->ref_list);
+    pj_list_init(&glock->ref_free_list);
+#endif
+
+    status = pj_atomic_create(pool, 0, &glock->ref_cnt);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    status = pj_lock_create_recursive_mutex(pool, pool->obj_name,
+                                            &glock->own_lock);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    own_lock = PJ_POOL_ZALLOC_T(pool, grp_lock_item);
+    own_lock->lock = glock->own_lock;
+    pj_list_push_back(&glock->lock_list, own_lock);
+
+    *p_grp_lock = glock;
+    return PJ_SUCCESS;
+
+on_error:
+    grp_lock_destroy(glock);
+    return status;
+}
+
+PJ_DEF(pj_status_t) pj_grp_lock_destroy( pj_grp_lock_t *grp_lock)
+{
+    return grp_lock_destroy(grp_lock);
+}
+
+PJ_DEF(pj_status_t) pj_grp_lock_acquire( pj_grp_lock_t *grp_lock)
+{
+    return grp_lock_acquire(grp_lock);
+}
+
+PJ_DEF(pj_status_t) pj_grp_lock_tryacquire( pj_grp_lock_t *grp_lock)
+{
+    return grp_lock_tryacquire(grp_lock);
+}
+
+PJ_DEF(pj_status_t) pj_grp_lock_release( pj_grp_lock_t *grp_lock)
+{
+    return grp_lock_release(grp_lock);
+}
+
+PJ_DEF(pj_status_t) pj_grp_lock_replace( pj_grp_lock_t *old,
+                                         pj_grp_lock_t *new)
+{
+    grp_destroy_callback *ocb;
+
+    /* Move handlers from old to new */
+    ocb = old->destroy_list.next;
+    while (ocb != &old->destroy_list) {
+	grp_destroy_callback *ncb;
+
+	ncb = PJ_POOL_ALLOC_T(new->pool, grp_destroy_callback);
+	ncb->comp = ocb->comp;
+	ncb->handler = ocb->handler;
+	pj_list_push_back(&new->destroy_list, ncb);
+
+	ocb = ocb->next;
+    }
+
+    pj_list_init(&old->destroy_list);
+
+    grp_lock_destroy(old);
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pj_grp_lock_add_handler( pj_grp_lock_t *glock,
+                                             pj_pool_t *pool,
+                                             void *comp,
+                                             void (*destroy)(void *comp))
+{
+    grp_destroy_callback *cb;
+
+    grp_lock_acquire(glock);
+
+    if (pool == NULL)
+	pool = glock->pool;
+
+    cb = PJ_POOL_ZALLOC_T(pool, grp_destroy_callback);
+    cb->comp = comp;
+    cb->handler = destroy;
+    pj_list_push_back(&glock->destroy_list, cb);
+
+    grp_lock_release(glock);
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pj_grp_lock_del_handler( pj_grp_lock_t *glock,
+                                             void *comp,
+                                             void (*destroy)(void *comp))
+{
+    grp_destroy_callback *cb;
+
+    grp_lock_acquire(glock);
+
+    cb = glock->destroy_list.next;
+    while (cb != &glock->destroy_list) {
+	if (cb->comp == comp && cb->handler == destroy)
+	    break;
+	cb = cb->next;
+    }
+
+    if (cb != &glock->destroy_list)
+	pj_list_erase(cb);
+
+    grp_lock_release(glock);
+    return PJ_SUCCESS;
+}
+
+static pj_status_t grp_lock_add_ref(pj_grp_lock_t *glock)
+{
+    pj_atomic_inc(glock->ref_cnt);
+    return PJ_SUCCESS;
+}
+
+static pj_status_t grp_lock_dec_ref(pj_grp_lock_t *glock)
+{
+    int cnt; /* for debugging */
+    if ((cnt=pj_atomic_dec_and_get(glock->ref_cnt)) == 0) {
+	grp_lock_destroy(glock);
+	return PJ_EGONE;
+    }
+    pj_assert(cnt > 0);
+    pj_grp_lock_dump(glock);
+    return PJ_SUCCESS;
+}
+
+#if PJ_GRP_LOCK_DEBUG
+PJ_DEF(pj_status_t) pj_grp_lock_add_ref_dbg(pj_grp_lock_t *glock,
+                                            const char *file,
+                                            int line)
+{
+    grp_lock_ref *ref;
+    pj_status_t status;
+
+    pj_enter_critical_section();
+    if (!pj_list_empty(&glock->ref_free_list)) {
+	ref = glock->ref_free_list.next;
+	pj_list_erase(ref);
+    } else {
+	ref = PJ_POOL_ALLOC_T(glock->pool, grp_lock_ref);
+    }
+
+    ref->file = file;
+    ref->line = line;
+    pj_list_push_back(&glock->ref_list, ref);
+
+    pj_leave_critical_section();
+
+    status = grp_lock_add_ref(glock);
+
+    if (status != PJ_SUCCESS) {
+	pj_enter_critical_section();
+	pj_list_erase(ref);
+	pj_list_push_back(&glock->ref_free_list, ref);
+	pj_leave_critical_section();
+    }
+
+    return status;
+}
+
+PJ_DEF(pj_status_t) pj_grp_lock_dec_ref_dbg(pj_grp_lock_t *glock,
+                                            const char *file,
+                                            int line)
+{
+    grp_lock_ref *ref;
+
+    pj_enter_critical_section();
+    /* Find the same source file */
+    ref = glock->ref_list.next;
+    while (ref != &glock->ref_list) {
+	if (strcmp(ref->file, file) == 0) {
+	    pj_list_erase(ref);
+	    pj_list_push_back(&glock->ref_free_list, ref);
+	    break;
+	}
+	ref = ref->next;
+    }
+    pj_leave_critical_section();
+
+    if (ref == &glock->ref_list) {
+	PJ_LOG(2,(THIS_FILE, "pj_grp_lock_dec_ref_dbg() could not find "
+			      "matching ref for %s", file));
+    }
+
+    return grp_lock_dec_ref(glock);
+}
+#else
+PJ_DEF(pj_status_t) pj_grp_lock_add_ref(pj_grp_lock_t *glock)
+{
+    return grp_lock_add_ref(glock);
+}
+
+PJ_DEF(pj_status_t) pj_grp_lock_dec_ref(pj_grp_lock_t *glock)
+{
+    return grp_lock_dec_ref(glock);
+}
+#endif
+
+PJ_DEF(int) pj_grp_lock_get_ref(pj_grp_lock_t *glock)
+{
+    return pj_atomic_get(glock->ref_cnt);
+}
+
+PJ_DEF(pj_status_t) pj_grp_lock_chain_lock( pj_grp_lock_t *glock,
+                                            pj_lock_t *lock,
+                                            int pos)
+{
+    grp_lock_item *lck, *new_lck;
+    unsigned i;
+
+    grp_lock_acquire(glock);
+
+    for (i=0; i<glock->owner_cnt; ++i)
+	pj_lock_acquire(lock);
+
+    lck = glock->lock_list.next;
+    while (lck != &glock->lock_list) {
+	if (lck->prio >= pos)
+	    break;
+	lck = lck->next;
+    }
+
+    new_lck = PJ_POOL_ZALLOC_T(glock->pool, grp_lock_item);
+    new_lck->prio = pos;
+    new_lck->lock = lock;
+    pj_list_insert_before(lck, new_lck);
+
+    /* this will also release the new lock */
+    grp_lock_release(glock);
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pj_grp_lock_unchain_lock( pj_grp_lock_t *glock,
+                                              pj_lock_t *lock)
+{
+    grp_lock_item *lck;
+
+    grp_lock_acquire(glock);
+
+    lck = glock->lock_list.next;
+    while (lck != &glock->lock_list) {
+	if (lck->lock == lock)
+	    break;
+	lck = lck->next;
+    }
+
+    if (lck != &glock->lock_list) {
+	unsigned i;
+
+	pj_list_erase(lck);
+	for (i=0; i<glock->owner_cnt; ++i)
+	    pj_lock_release(lck->lock);
+    }
+
+    grp_lock_release(glock);
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(void) pj_grp_lock_dump(pj_grp_lock_t *grp_lock)
+{
+#if PJ_GRP_LOCK_DEBUG
+    grp_lock_ref *ref = grp_lock->ref_list.next;
+    char info_buf[1000];
+    pj_str_t info;
+
+    info.ptr = info_buf;
+    info.slen = 0;
+
+    pj_grp_lock_acquire(grp_lock);
+    pj_enter_critical_section();
+
+    while (ref != &grp_lock->ref_list && info.slen < sizeof(info_buf)) {
+	char *start = info.ptr + info.slen;
+	int max_len = sizeof(info_buf) - info.slen;
+	int len;
+
+	len = pj_ansi_snprintf(start, max_len, "%s:%d ", ref->file, ref->line);
+	if (len < 1 || len > max_len) {
+	    len = strlen(ref->file);
+	    if (len > max_len - 1)
+		len = max_len - 1;
+
+	    memcpy(start, ref->file, len);
+	    start[len++] = ' ';
+	}
+
+	info.slen += len;
+
+	ref = ref->next;
+    }
+
+    if (ref != &grp_lock->ref_list) {
+	int i;
+	for (i=0; i<4; ++i)
+	    info_buf[sizeof(info_buf)-i-1] = '.';
+    }
+    info.ptr[info.slen-1] = '\0';
+
+    pj_leave_critical_section();
+    pj_grp_lock_release(grp_lock);
+
+    PJ_LOG(4,(THIS_FILE, "Group lock %p, ref_cnt=%d. Reference holders: %s",
+	       grp_lock, pj_grp_lock_get_ref(grp_lock), info.ptr));
+#endif
+}
