blob: d7f4ffaf51dde8637ce8627254226ae4189642cc [file] [log] [blame]
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pj/lock.h>
#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;
/*
* Lock structure.
*/
struct pj_lock_t
{
LOCK_OBJ *lock_object;
pj_status_t (*acquire) (LOCK_OBJ*);
pj_status_t (*tryacquire) (LOCK_OBJ*);
pj_status_t (*release) (LOCK_OBJ*);
pj_status_t (*destroy) (LOCK_OBJ*);
};
typedef pj_status_t (*FPTR)(LOCK_OBJ*);
/******************************************************************************
* Implementation of lock object with mutex.
*/
static pj_lock_t mutex_lock_template =
{
NULL,
(FPTR) &pj_mutex_lock,
(FPTR) &pj_mutex_trylock,
(FPTR) &pj_mutex_unlock,
(FPTR) &pj_mutex_destroy
};
static pj_status_t create_mutex_lock( pj_pool_t *pool,
const char *name,
int type,
pj_lock_t **lock )
{
pj_lock_t *p_lock;
pj_mutex_t *mutex;
pj_status_t rc;
PJ_ASSERT_RETURN(pool && lock, PJ_EINVAL);
p_lock = PJ_POOL_ALLOC_T(pool, pj_lock_t);
if (!p_lock)
return PJ_ENOMEM;
pj_memcpy(p_lock, &mutex_lock_template, sizeof(pj_lock_t));
rc = pj_mutex_create(pool, name, type, &mutex);
if (rc != PJ_SUCCESS)
return rc;
p_lock->lock_object = mutex;
*lock = p_lock;
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pj_lock_create_simple_mutex( pj_pool_t *pool,
const char *name,
pj_lock_t **lock )
{
return create_mutex_lock(pool, name, PJ_MUTEX_SIMPLE, lock);
}
PJ_DEF(pj_status_t) pj_lock_create_recursive_mutex( pj_pool_t *pool,
const char *name,
pj_lock_t **lock )
{
return create_mutex_lock(pool, name, PJ_MUTEX_RECURSE, lock);
}
/******************************************************************************
* Implementation of NULL lock object.
*/
static pj_status_t null_op(void *arg)
{
PJ_UNUSED_ARG(arg);
return PJ_SUCCESS;
}
static pj_lock_t null_lock_template =
{
NULL,
&null_op,
&null_op,
&null_op,
&null_op
};
PJ_DEF(pj_status_t) pj_lock_create_null_mutex( pj_pool_t *pool,
const char *name,
pj_lock_t **lock )
{
PJ_UNUSED_ARG(name);
PJ_UNUSED_ARG(pool);
PJ_ASSERT_RETURN(lock, PJ_EINVAL);
*lock = &null_lock_template;
return PJ_SUCCESS;
}
/******************************************************************************
* Implementation of semaphore lock object.
*/
#if defined(PJ_HAS_SEMAPHORE) && PJ_HAS_SEMAPHORE != 0
static pj_lock_t sem_lock_template =
{
NULL,
(FPTR) &pj_sem_wait,
(FPTR) &pj_sem_trywait,
(FPTR) &pj_sem_post,
(FPTR) &pj_sem_destroy
};
PJ_DEF(pj_status_t) pj_lock_create_semaphore( pj_pool_t *pool,
const char *name,
unsigned initial,
unsigned max,
pj_lock_t **lock )
{
pj_lock_t *p_lock;
pj_sem_t *sem;
pj_status_t rc;
PJ_ASSERT_RETURN(pool && lock, PJ_EINVAL);
p_lock = PJ_POOL_ALLOC_T(pool, pj_lock_t);
if (!p_lock)
return PJ_ENOMEM;
pj_memcpy(p_lock, &sem_lock_template, sizeof(pj_lock_t));
rc = pj_sem_create( pool, name, initial, max, &sem);
if (rc != PJ_SUCCESS)
return rc;
p_lock->lock_object = sem;
*lock = p_lock;
return PJ_SUCCESS;
}
#endif /* PJ_HAS_SEMAPHORE */
PJ_DEF(pj_status_t) pj_lock_acquire( pj_lock_t *lock )
{
PJ_ASSERT_RETURN(lock != NULL, PJ_EINVAL);
return (*lock->acquire)(lock->lock_object);
}
PJ_DEF(pj_status_t) pj_lock_tryacquire( pj_lock_t *lock )
{
PJ_ASSERT_RETURN(lock != NULL, PJ_EINVAL);
return (*lock->tryacquire)(lock->lock_object);
}
PJ_DEF(pj_status_t) pj_lock_release( pj_lock_t *lock )
{
PJ_ASSERT_RETURN(lock != NULL, PJ_EINVAL);
return (*lock->release)(lock->lock_object);
}
PJ_DEF(pj_status_t) pj_lock_destroy( pj_lock_t *lock )
{
PJ_ASSERT_RETURN(lock != NULL, PJ_EINVAL);
return (*lock->destroy)(lock->lock_object);
}
/******************************************************************************
* 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) {
int 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_lock,
pj_grp_lock_t *new_lock)
{
grp_destroy_callback *ocb;
/* Move handlers from old to new */
ocb = old_lock->destroy_list.next;
while (ocb != &old_lock->destroy_list) {
grp_destroy_callback *ncb;
ncb = PJ_POOL_ALLOC_T(new_lock->pool, grp_destroy_callback);
ncb->comp = ocb->comp;
ncb->handler = ocb->handler;
pj_list_push_back(&new_lock->destroy_list, ncb);
ocb = ocb->next;
}
pj_list_init(&old_lock->destroy_list);
grp_lock_destroy(old_lock);
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;
int 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) {
int 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));
#else
PJ_UNUSED_ARG(grp_lock);
#endif
}