/*
**  pth_sync.c -- Pth synchronization facilities
**
**  Copyright (c) 1999 Ralf S. Engelschall <rse@engelschall.com>
**
**  This file is part of GNU Pth, a non-preemptive thread scheduling
**  library which can be found at http://www.gnu.org/software/pth/.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Library General Public
**  License as published by the Free Software Foundation; either
**  version 2 of the License, or (at your option) any later version.
**
**  This library 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
**  Library General Public License for more details.
**
**  You should have received a copy of the GNU Library General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
**  USA, or contact Ralf S. Engelschall <rse@engelschall.com>.
*/

#include "pth_p.h"

/*
**  Mutual Exclusion Locks
*/

int pth_mutex_init(pth_mutex_t *mutex)
{
    if (mutex == NULL)
        return_errno(FALSE, EINVAL);
    if (mutex->mx_state & PTH_MUTEX_LOCKED)
        return_errno(FALSE, EBUSY);
    pth_ring_init(NULL, &mutex->mx_node);
    mutex->mx_state = PTH_MUTEX_INITIALIZED;
    mutex->mx_owner = NULL;
    mutex->mx_count = 0;
    return TRUE;
}

int pth_mutex_acquire(pth_mutex_t *mutex, int try, pth_event_t ev_extra)
{
    static pth_key_t ev_key = PTH_KEY_INIT;
    pth_event_t ev;

    pth_debug2("pth_mutex_acquire: called from thread \"%s\"", pth_current->name);

    /* consistency checks */
    if (mutex == NULL)
        return_errno(FALSE, EINVAL);
    if (!(mutex->mx_state & PTH_MUTEX_INITIALIZED))
        return_errno(FALSE, EDEADLK);

    /* still not locked, so simply acquire mutex? */
    if (!(mutex->mx_state & PTH_MUTEX_LOCKED)) {
        mutex->mx_state |= PTH_MUTEX_LOCKED;
        mutex->mx_owner = pth_current;
        mutex->mx_count = 1;
        pth_ring_append(&(pth_current->mutexring), &(mutex->mx_node));
        pth_debug1("pth_mutex_acquire: immediately locking mutex"); 
        return TRUE;
    }
    
    /* already locked by caller? */
    if (mutex->mx_count >= 1 && mutex->mx_owner == pth_current) {
        /* recursive lock */
        mutex->mx_count++;
        pth_debug1("pth_mutex_acquire: recursive locking"); 
        return TRUE;
    }

    /* should we should try? */
    if (try)
        return_errno(FALSE, EAGAIN); 

    /* else wait for mutex to become unlocked.. */
    pth_debug1("pth_mutex_acquire: wait until mutex is unlocked"); 
    while (1) {
        ev = pth_event(PTH_EVENT_MUTEX|PTH_MODE_STATIC, &ev_key, mutex);
        if (ev_extra != NULL)
            pth_event_concat(ev, ev_extra, NULL);
        pth_wait(ev);
        if (ev_extra != NULL) {
            pth_event_isolate(ev);
            if (!pth_event_occurred(ev))
                return_errno(FALSE, EINTR);
        }
        if (!(mutex->mx_state & PTH_MUTEX_LOCKED))
            break;
    }

    /* now it's again unlocked, so acquire mutex */
    pth_debug1("pth_mutex_acquire: locking mutex"); 
    mutex->mx_state |= PTH_MUTEX_LOCKED;
    mutex->mx_owner = pth_current;
    mutex->mx_count = 1;
    pth_ring_append(&(pth_current->mutexring), &(mutex->mx_node));
    return TRUE;
}

int pth_mutex_release(pth_mutex_t *mutex)
{
    /* consistency checks */
    if (mutex == NULL)
        return_errno(FALSE, EINVAL);
    if (!(mutex->mx_state & PTH_MUTEX_INITIALIZED))
        return_errno(FALSE, EDEADLK);
    if (!(mutex->mx_state & PTH_MUTEX_LOCKED))
        return_errno(FALSE, EDEADLK);
    if (mutex->mx_owner != pth_current)
        return_errno(FALSE, EACCES);

    /* decrement recursion counter and release mutex */
    mutex->mx_count--;
    if (mutex->mx_count <= 0) {
        mutex->mx_state &= ~(PTH_MUTEX_LOCKED);
        mutex->mx_owner = NULL;
        mutex->mx_count = 0;
        pth_ring_remove(&(pth_current->mutexring), &(mutex->mx_node));
    }
    return TRUE;
}

intern int pth_mutex_releaseall(pth_t thread)
{
    pth_ringnode_t *rn;

    if (thread == NULL)
        return_errno(FALSE, EINVAL);
    /* iterate over all mutexes of thread */
    for (rn = pth_ring_first(&(thread->mutexring)); rn != NULL;
         rn = pth_ring_next(&(thread->mutexring), rn)          )
        if (!pth_mutex_release((pth_mutex_t *)rn))
            return FALSE;
    return TRUE;
}

/*
**  Read-Write Locks
*/

int pth_rwlock_init(pth_rwlock_t *rwlock)
{
    if (rwlock == NULL)
        return_errno(FALSE, EINVAL);
    rwlock->rw_state = PTH_RWLOCK_INITIALIZED;
    rwlock->rw_readers = 0;
    pth_mutex_init(&(rwlock->rw_mutex_rd));
    pth_mutex_init(&(rwlock->rw_mutex_rw));
    return TRUE;
}

int pth_rwlock_acquire(pth_rwlock_t *rwlock, int op, int try, pth_event_t ev_extra)
{
    /* consistency checks */
    if (rwlock == NULL)
        return_errno(FALSE, EINVAL);
    if (!(rwlock->rw_state & PTH_RWLOCK_INITIALIZED))
        return_errno(FALSE, EDEADLK);

    /* acquire lock */
    if (op == PTH_RWLOCK_RW) {
        /* read-write lock is simple */
        if (!pth_mutex_acquire(&(rwlock->rw_mutex_rw), try, ev_extra))
            return FALSE;
        rwlock->rw_mode = PTH_RWLOCK_RW;
    }
    else {
        /* read-only lock is more complicated to get right */
        if (!pth_mutex_acquire(&(rwlock->rw_mutex_rd), try, ev_extra))
            return FALSE;
        rwlock->rw_readers++;
        if (rwlock->rw_readers == 1) {
            if (!pth_mutex_acquire(&(rwlock->rw_mutex_rw), try, ev_extra)) {
                rwlock->rw_readers--;
                errno_preserve;
                pth_mutex_release(&(rwlock->rw_mutex_rd));
                errno_restore;
                return FALSE;
            }
        }
        rwlock->rw_mode = PTH_RWLOCK_RD;
        pth_mutex_release(&(rwlock->rw_mutex_rd));
    }
    return TRUE;
}

int pth_rwlock_release(pth_rwlock_t *rwlock)
{
    /* consistency checks */
    if (rwlock == NULL)
        return_errno(FALSE, EINVAL);
    if (!(rwlock->rw_state & PTH_RWLOCK_INITIALIZED))
        return_errno(FALSE, EDEADLK);

    /* release lock */
    if (rwlock->rw_mode == PTH_RWLOCK_RW) {
        /* read-write unlock is simple */
        if (!pth_mutex_release(&(rwlock->rw_mutex_rw)))
            return FALSE;
    }
    else {
        /* read-only unlock is more complicated to get right */
        if (!pth_mutex_acquire(&(rwlock->rw_mutex_rd), FALSE, NULL))
            return FALSE;
        rwlock->rw_readers--;
        if (rwlock->rw_readers == 0) {
            if (!pth_mutex_release(&(rwlock->rw_mutex_rw))) {
                rwlock->rw_readers++;
                errno_preserve;
                pth_mutex_release(&(rwlock->rw_mutex_rd));
                errno_restore;
                return FALSE;
            }
        }
        rwlock->rw_mode = PTH_RWLOCK_RD;
        pth_mutex_release(&(rwlock->rw_mutex_rd));
    }
    return TRUE;
}

/*
**  Condition Variables
*/

int pth_cond_init(pth_cond_t *cond)
{
    if (cond == NULL)
        return_errno(FALSE, EINVAL);
    cond->cn_state = PTH_COND_INITIALIZED;
    return TRUE;
}

int pth_cond_await(pth_cond_t *cond, pth_mutex_t *mutex, pth_event_t ev_extra)
{
    static pth_key_t ev_key = PTH_KEY_INIT;
    pth_event_t ev;

    /* consistency checks */
    if (cond == NULL || mutex == NULL)
        return_errno(FALSE, EINVAL);
    if (!(cond->cn_state & PTH_COND_INITIALIZED))
        return_errno(FALSE, EDEADLK);

    /* check whether we can do a short-circuit wait */
    if (    (cond->cn_state & PTH_COND_SIGNALED)
        && !(cond->cn_state & PTH_COND_BROADCAST)) {
        cond->cn_state &= ~(PTH_COND_SIGNALED);
        cond->cn_state &= ~(PTH_COND_BROADCAST);
        cond->cn_state &= ~(PTH_COND_HANDLED);
        return TRUE;
    }

    /* release mutex (caller had to acquire it first) */
    pth_mutex_release(mutex);

    /* wait until the condition is signaled */
    ev = pth_event(PTH_EVENT_COND|PTH_MODE_STATIC, &ev_key, cond);
    if (ev_extra != NULL)
        pth_event_concat(ev, ev_extra, NULL);
    pth_wait(ev);
    if (ev_extra != NULL)
        pth_event_isolate(ev);

    /* reacquire mutex */
    pth_mutex_acquire(mutex, FALSE, NULL);
    return TRUE;
}

int pth_cond_notify(pth_cond_t *cond, int broadcast)
{
    /* consistency checks */
    if (cond == NULL)
        return_errno(FALSE, EINVAL);
    if (!(cond->cn_state & PTH_COND_INITIALIZED))
        return_errno(FALSE, EDEADLK);

    /* signal the condition */
    cond->cn_state |= PTH_COND_SIGNALED;
    if (broadcast)
        cond->cn_state |= PTH_COND_BROADCAST;
    else
        cond->cn_state &= ~(PTH_COND_BROADCAST);
    cond->cn_state &= ~(PTH_COND_HANDLED);

    /* and give other threads a chance to awake */
    pth_yield(NULL);

    /* return to caller */
    return TRUE;
}

