root/lib/libc/thread/rthread_sync.c
/*      $OpenBSD: rthread_sync.c,v 1.7 2026/03/27 12:26:58 claudio Exp $ */
/*
 * Copyright (c) 2004,2005 Ted Unangst <tedu@openbsd.org>
 * Copyright (c) 2012 Philip Guenther <guenther@openbsd.org>
 * All Rights Reserved.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
/*
 * Mutexes and conditions - synchronization functions.
 */

#include <assert.h>
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "rthread.h"
#include "cancel.h"             /* in libc/include */

static _atomic_lock_t static_init_lock = _SPINLOCK_UNLOCKED;

/*
 * mutexen
 */
int
pthread_mutex_init(pthread_mutex_t *mutexp, const pthread_mutexattr_t *attr)
{
        struct pthread_mutex *mutex;

        mutex = calloc(1, sizeof(*mutex));
        if (!mutex)
                return (errno);
        mutex->lock = _SPINLOCK_UNLOCKED;
        TAILQ_INIT(&mutex->lockers);
        if (attr == NULL) {
                mutex->type = PTHREAD_MUTEX_DEFAULT;
                mutex->prioceiling = -1;
        } else {
                mutex->type = (*attr)->ma_type;
                mutex->prioceiling = (*attr)->ma_protocol ==
                    PTHREAD_PRIO_PROTECT ? (*attr)->ma_prioceiling : -1;
        }
        *mutexp = mutex;

        return (0);
}
DEF_STRONG(pthread_mutex_init);

int
pthread_mutex_destroy(pthread_mutex_t *mutexp)
{
        struct pthread_mutex *mutex;

        if (mutexp == NULL)
                return (EINVAL);

        mutex = (struct pthread_mutex *)*mutexp;
        if (mutex) {
                if (mutex->count || mutex->owner != NULL ||
                    !TAILQ_EMPTY(&mutex->lockers)) {
#define MSG "pthread_mutex_destroy on mutex with waiters!\n"
                        write(2, MSG, sizeof(MSG) - 1);
#undef MSG
                        return (EBUSY);
                }
                free(mutex);
                *mutexp = NULL;
        }
        return (0);
}
DEF_STRONG(pthread_mutex_destroy);

static int
_rthread_mutex_lock(pthread_mutex_t *mutexp, int trywait,
    const struct timespec *abstime)
{
        struct pthread_mutex *mutex;
        pthread_t self = pthread_self();
        int ret = 0;

        /*
         * If the mutex is statically initialized, perform the dynamic
         * initialization. Note: _thread_mutex_lock() in libc requires
         * _rthread_mutex_lock() to perform the mutex init when *mutexp
         * is NULL.
         */
        if (*mutexp == NULL) {
                _spinlock(&static_init_lock);
                if (*mutexp == NULL)
                        ret = pthread_mutex_init(mutexp, NULL);
                _spinunlock(&static_init_lock);
                if (ret != 0)
                        return (EINVAL);
        }
        mutex = (struct pthread_mutex *)*mutexp;

        _rthread_debug(5, "%p: mutex_lock %p\n", (void *)self, (void *)mutex);
        _spinlock(&mutex->lock);
        if (mutex->owner == NULL && TAILQ_EMPTY(&mutex->lockers)) {
                assert(mutex->count == 0);
                mutex->owner = self;
        } else if (mutex->owner == self) {
                assert(mutex->count > 0);

                /* already owner?  handle recursive behavior */
                if (mutex->type != PTHREAD_MUTEX_RECURSIVE)
                {
                        if (trywait ||
                            mutex->type == PTHREAD_MUTEX_ERRORCHECK) {
                                _spinunlock(&mutex->lock);
                                return (trywait ? EBUSY : EDEADLK);
                        }

                        /* self-deadlock is disallowed by strict */
                        if (mutex->type == PTHREAD_MUTEX_STRICT_NP &&
                            abstime == NULL)
                                abort();

                        /* self-deadlock, possibly until timeout */
                        while (__thrsleep(self, CLOCK_REALTIME, abstime,
                            &mutex->lock, NULL) != EWOULDBLOCK)
                                _spinlock(&mutex->lock);
                        return (ETIMEDOUT);
                }
                if (mutex->count == INT_MAX) {
                        _spinunlock(&mutex->lock);
                        return (EAGAIN);
                }
        } else if (trywait) {
                /* try failed */
                _spinunlock(&mutex->lock);
                return (EBUSY);
        } else {
                /* add to the wait queue and block until at the head */
                TAILQ_INSERT_TAIL(&mutex->lockers, self, waiting);
                while (mutex->owner != self) {
                        ret = __thrsleep(self, CLOCK_REALTIME, abstime,
                            &mutex->lock, NULL);
                        _spinlock(&mutex->lock);
                        assert(mutex->owner != NULL);
                        if (ret == EWOULDBLOCK) {
                                if (mutex->owner == self)
                                        break;
                                TAILQ_REMOVE(&mutex->lockers, self, waiting);
                                _spinunlock(&mutex->lock);
                                return (ETIMEDOUT);
                        }
                }
        }

        mutex->count++;
        _spinunlock(&mutex->lock);

        return (0);
}

int
pthread_mutex_lock(pthread_mutex_t *p)
{
        return (_rthread_mutex_lock(p, 0, NULL));
}
DEF_STRONG(pthread_mutex_lock);

int
pthread_mutex_trylock(pthread_mutex_t *p)
{
        return (_rthread_mutex_lock(p, 1, NULL));
}

int
pthread_mutex_timedlock(pthread_mutex_t *p, const struct timespec *abstime)
{
        return (_rthread_mutex_lock(p, 0, abstime));
}

int
pthread_mutex_unlock(pthread_mutex_t *mutexp)
{
        pthread_t self = pthread_self();
        struct pthread_mutex *mutex = (struct pthread_mutex *)*mutexp;

        _rthread_debug(5, "%p: mutex_unlock %p\n", (void *)self,
            (void *)mutex);

        if (mutex == NULL)
#if PTHREAD_MUTEX_DEFAULT == PTHREAD_MUTEX_ERRORCHECK
                return (EPERM);
#elif PTHREAD_MUTEX_DEFAULT == PTHREAD_MUTEX_NORMAL
                return(0);
#else
                abort();
#endif

        if (mutex->owner != self) {
                if (mutex->type == PTHREAD_MUTEX_ERRORCHECK ||
                    mutex->type == PTHREAD_MUTEX_RECURSIVE)
                        return (EPERM);
                else {
                        /*
                         * For mutex type NORMAL our undefined behavior for
                         * unlocking an unlocked mutex is to succeed without
                         * error.  All other undefined behaviors are to
                         * abort() immediately.
                         */
                        if (mutex->owner == NULL &&
                            mutex->type == PTHREAD_MUTEX_NORMAL)
                                return (0);
                        else
                                abort();
                }
        }

        if (--mutex->count == 0) {
                pthread_t next;

                _spinlock(&mutex->lock);
                mutex->owner = next = TAILQ_FIRST(&mutex->lockers);
                if (next != NULL)
                        TAILQ_REMOVE(&mutex->lockers, next, waiting);
                _spinunlock(&mutex->lock);
                if (next != NULL)
                        __thrwakeup(next, 1);
        }

        return (0);
}
DEF_STRONG(pthread_mutex_unlock);

/*
 * condition variables
 */
int
pthread_cond_init(pthread_cond_t *condp, const pthread_condattr_t *attr)
{
        pthread_cond_t cond;

        cond = calloc(1, sizeof(*cond));
        if (!cond)
                return (errno);
        cond->lock = _SPINLOCK_UNLOCKED;
        TAILQ_INIT(&cond->waiters);
        if (attr == NULL)
                cond->clock = CLOCK_REALTIME;
        else
                cond->clock = (*attr)->ca_clock;
        *condp = cond;

        return (0);
}
DEF_STRONG(pthread_cond_init);

int
pthread_cond_destroy(pthread_cond_t *condp)
{
        pthread_cond_t cond;

        assert(condp);
        cond = *condp;
        if (cond) {
                if (!TAILQ_EMPTY(&cond->waiters)) {
#define MSG "pthread_cond_destroy on condvar with waiters!\n"
                        write(2, MSG, sizeof(MSG) - 1);
#undef MSG
                        return (EBUSY);
                }
                free(cond);
        }
        *condp = NULL;

        return (0);
}

int
pthread_cond_timedwait(pthread_cond_t *condp, pthread_mutex_t *mutexp,
    const struct timespec *abstime)
{
        pthread_cond_t cond;
        struct pthread_mutex *mutex = (struct pthread_mutex *)*mutexp;
        struct tib *tib = TIB_GET();
        pthread_t self = tib->tib_thread;
        pthread_t next;
        int mutex_count;
        int canceled = 0;
        int rv = 0;
        int error;
        PREP_CANCEL_POINT(tib);

        if (!*condp)
                if ((error = pthread_cond_init(condp, NULL)))
                        return (error);
        cond = *condp;
        _rthread_debug(5, "%p: cond_timed %p,%p\n", (void *)self,
            (void *)cond, (void *)mutex);

        if (mutex == NULL)
#if PTHREAD_MUTEX_DEFAULT == PTHREAD_MUTEX_ERRORCHECK
                return (EPERM);
#else
                abort();
#endif

        if (mutex->owner != self) {
                if (mutex->type == PTHREAD_MUTEX_ERRORCHECK)
                        return (EPERM);
                else
                        abort();
        }

        if (abstime == NULL || abstime->tv_nsec < 0 ||
            abstime->tv_nsec >= 1000000000)
                return (EINVAL);

        ENTER_DELAYED_CANCEL_POINT(tib, self);

        _spinlock(&cond->lock);

        /* mark the condvar as being associated with this mutex */
        if (cond->mutex == NULL) {
                cond->mutex = mutex;
                assert(TAILQ_EMPTY(&cond->waiters));
        } else if (cond->mutex != mutex) {
                assert(cond->mutex == mutex);
                _spinunlock(&cond->lock);
                LEAVE_CANCEL_POINT_INNER(tib, 1);
                return (EINVAL);
        } else
                assert(! TAILQ_EMPTY(&cond->waiters));

        /* snag the count in case this is a recursive mutex */
        mutex_count = mutex->count;

        /* transfer from the mutex queue to the condvar queue */
        _spinlock(&mutex->lock);
        self->blocking_cond = cond;
        TAILQ_INSERT_TAIL(&cond->waiters, self, waiting);
        _spinunlock(&cond->lock);

        /* wake the next guy blocked on the mutex */
        mutex->count = 0;
        mutex->owner = next = TAILQ_FIRST(&mutex->lockers);
        if (next != NULL) {
                TAILQ_REMOVE(&mutex->lockers, next, waiting);
                __thrwakeup(next, 1);
        }

        /* wait until we're the owner of the mutex again */
        while (mutex->owner != self) {
                error = __thrsleep(self, cond->clock, abstime,
                    &mutex->lock, &self->delayed_cancel);

                /*
                 * If abstime == NULL, then we're definitely waiting
                 * on the mutex instead of the condvar, and are
                 * just waiting for mutex ownership, regardless of
                 * why we woke up.
                 */
                if (abstime == NULL) {
                        _spinlock(&mutex->lock);
                        continue;
                }

                /*
                 * If we took a normal signal (not from
                 * cancellation) then we should just go back to
                 * sleep without changing state (timeouts, etc).
                 */
                if ((error == EINTR || error == ECANCELED) &&
                    (tib->tib_canceled == 0 ||
                    (tib->tib_cantcancel & CANCEL_DISABLED))) {
                        _spinlock(&mutex->lock);
                        continue;
                }

                /*
                 * The remaining reasons for waking up (normal
                 * wakeup, timeout, and cancellation) all mean that
                 * we won't be staying in the condvar queue and
                 * we'll no longer time out or be cancelable.
                 */
                abstime = NULL;
                LEAVE_CANCEL_POINT_INNER(tib, 0);

                /*
                 * If we're no longer in the condvar's queue then
                 * we're just waiting for mutex ownership.  Need
                 * cond->lock here to prevent race with cond_signal().
                 */
                _spinlock(&cond->lock);
                if (self->blocking_cond == NULL) {
                        _spinunlock(&cond->lock);
                        _spinlock(&mutex->lock);
                        continue;
                }
                assert(self->blocking_cond == cond);

                /* if timeout or canceled, make note of that */
                if (error == EWOULDBLOCK)
                        rv = ETIMEDOUT;
                else if (error == EINTR)
                        canceled = 1;

                /* transfer between the queues */
                TAILQ_REMOVE(&cond->waiters, self, waiting);
                assert(mutex == cond->mutex);
                if (TAILQ_EMPTY(&cond->waiters))
                        cond->mutex = NULL;
                self->blocking_cond = NULL;
                _spinunlock(&cond->lock);
                _spinlock(&mutex->lock);

                /* mutex unlocked right now? */
                if (mutex->owner == NULL &&
                    TAILQ_EMPTY(&mutex->lockers)) {
                        assert(mutex->count == 0);
                        mutex->owner = self;
                        break;
                }
                TAILQ_INSERT_TAIL(&mutex->lockers, self, waiting);
        }

        /* restore the mutex's count */
        mutex->count = mutex_count;
        _spinunlock(&mutex->lock);

        LEAVE_CANCEL_POINT_INNER(tib, canceled);

        return (rv);
}

int
pthread_cond_wait(pthread_cond_t *condp, pthread_mutex_t *mutexp)
{
        pthread_cond_t cond;
        struct pthread_mutex *mutex = (struct pthread_mutex *)*mutexp;
        struct tib *tib = TIB_GET();
        pthread_t self = tib->tib_thread;
        pthread_t next;
        int mutex_count;
        int canceled = 0;
        int error;
        PREP_CANCEL_POINT(tib);

        if (!*condp)
                if ((error = pthread_cond_init(condp, NULL)))
                        return (error);
        cond = *condp;
        _rthread_debug(5, "%p: cond_wait %p,%p\n", (void *)self,
            (void *)cond, (void *)mutex);

        if (mutex == NULL)
#if PTHREAD_MUTEX_DEFAULT == PTHREAD_MUTEX_ERRORCHECK
                return (EPERM);
#else
                abort();
#endif

        if (mutex->owner != self) {
                if (mutex->type == PTHREAD_MUTEX_ERRORCHECK)
                        return (EPERM);
                else
                        abort();
        }

        ENTER_DELAYED_CANCEL_POINT(tib, self);

        _spinlock(&cond->lock);

        /* mark the condvar as being associated with this mutex */
        if (cond->mutex == NULL) {
                cond->mutex = mutex;
                assert(TAILQ_EMPTY(&cond->waiters));
        } else if (cond->mutex != mutex) {
                assert(cond->mutex == mutex);
                _spinunlock(&cond->lock);
                LEAVE_CANCEL_POINT_INNER(tib, 1);
                return (EINVAL);
        } else
                assert(! TAILQ_EMPTY(&cond->waiters));

        /* snag the count in case this is a recursive mutex */
        mutex_count = mutex->count;

        /* transfer from the mutex queue to the condvar queue */
        _spinlock(&mutex->lock);
        self->blocking_cond = cond;
        TAILQ_INSERT_TAIL(&cond->waiters, self, waiting);
        _spinunlock(&cond->lock);

        /* wake the next guy blocked on the mutex */
        mutex->count = 0;
        mutex->owner = next = TAILQ_FIRST(&mutex->lockers);
        if (next != NULL) {
                TAILQ_REMOVE(&mutex->lockers, next, waiting);
                __thrwakeup(next, 1);
        }

        /* wait until we're the owner of the mutex again */
        while (mutex->owner != self) {
                error = __thrsleep(self, 0, NULL, &mutex->lock,
                    &self->delayed_cancel);

                /*
                 * If we took a normal signal (not from
                 * cancellation) then we should just go back to
                 * sleep without changing state (timeouts, etc).
                 */
                if ((error == EINTR || error == ECANCELED) &&
                    (tib->tib_canceled == 0 ||
                    (tib->tib_cantcancel & CANCEL_DISABLED))) {
                        _spinlock(&mutex->lock);
                        continue;
                }

                /*
                 * The remaining reasons for waking up (normal
                 * wakeup and cancellation) all mean that we won't
                 * be staying in the condvar queue and we'll no
                 * longer be cancelable.
                 */
                LEAVE_CANCEL_POINT_INNER(tib, 0);

                /*
                 * If we're no longer in the condvar's queue then
                 * we're just waiting for mutex ownership.  Need
                 * cond->lock here to prevent race with cond_signal().
                 */
                _spinlock(&cond->lock);
                if (self->blocking_cond == NULL) {
                        _spinunlock(&cond->lock);
                        _spinlock(&mutex->lock);
                        continue;
                }
                assert(self->blocking_cond == cond);

                /* if canceled, make note of that */
                if (error == EINTR)
                        canceled = 1;

                /* transfer between the queues */
                TAILQ_REMOVE(&cond->waiters, self, waiting);
                assert(mutex == cond->mutex);
                if (TAILQ_EMPTY(&cond->waiters))
                        cond->mutex = NULL;
                self->blocking_cond = NULL;
                _spinunlock(&cond->lock);
                _spinlock(&mutex->lock);

                /* mutex unlocked right now? */
                if (mutex->owner == NULL &&
                    TAILQ_EMPTY(&mutex->lockers)) {
                        assert(mutex->count == 0);
                        mutex->owner = self;
                        break;
                }
                TAILQ_INSERT_TAIL(&mutex->lockers, self, waiting);
        }

        /* restore the mutex's count */
        mutex->count = mutex_count;
        _spinunlock(&mutex->lock);

        LEAVE_CANCEL_POINT_INNER(tib, canceled);

        return (0);
}


int
pthread_cond_signal(pthread_cond_t *condp)
{
        pthread_cond_t cond;
        struct pthread_mutex *mutex;
        pthread_t thread;
        int wakeup;

        /* uninitialized?  Then there's obviously no one waiting! */
        if (!*condp)
                return 0;

        cond = *condp;
        _rthread_debug(5, "%p: cond_signal %p,%p\n", (void *)pthread_self(),
            (void *)cond, (void *)cond->mutex);
        _spinlock(&cond->lock);
        thread = TAILQ_FIRST(&cond->waiters);
        if (thread == NULL) {
                assert(cond->mutex == NULL);
                _spinunlock(&cond->lock);
                return (0);
        }

        assert(thread->blocking_cond == cond);
        TAILQ_REMOVE(&cond->waiters, thread, waiting);
        thread->blocking_cond = NULL;

        mutex = cond->mutex;
        assert(mutex != NULL);
        if (TAILQ_EMPTY(&cond->waiters))
                cond->mutex = NULL;

        /* link locks to prevent race with timedwait */
        _spinlock(&mutex->lock);
        _spinunlock(&cond->lock);

        wakeup = mutex->owner == NULL && TAILQ_EMPTY(&mutex->lockers);
        if (wakeup)
                mutex->owner = thread;
        else
                TAILQ_INSERT_TAIL(&mutex->lockers, thread, waiting);
        _spinunlock(&mutex->lock);
        if (wakeup)
                __thrwakeup(thread, 1);

        return (0);
}

int
pthread_cond_broadcast(pthread_cond_t *condp)
{
        pthread_cond_t cond;
        struct pthread_mutex *mutex;
        pthread_t thread;
        pthread_t p;
        int wakeup;

        /* uninitialized?  Then there's obviously no one waiting! */
        if (!*condp)
                return 0;

        cond = *condp;
        _rthread_debug(5, "%p: cond_broadcast %p,%p\n", (void *)pthread_self(),
            (void *)cond, (void *)cond->mutex);
        _spinlock(&cond->lock);
        thread = TAILQ_FIRST(&cond->waiters);
        if (thread == NULL) {
                assert(cond->mutex == NULL);
                _spinunlock(&cond->lock);
                return (0);
        }

        mutex = cond->mutex;
        assert(mutex != NULL);

        /* walk the list, clearing the "blocked on condvar" pointer */
        p = thread;
        do
                p->blocking_cond = NULL;
        while ((p = TAILQ_NEXT(p, waiting)) != NULL);

        /*
         * We want to transfer all the threads from the condvar's list
         * to the mutex's list.  The TAILQ_* macros don't let us do that
         * efficiently, so this is direct list surgery.  Pay attention!
         */

        /* 1) attach the first thread to the end of the mutex's list */
        _spinlock(&mutex->lock);
        wakeup = mutex->owner == NULL && TAILQ_EMPTY(&mutex->lockers);
        thread->waiting.tqe_prev = mutex->lockers.tqh_last;
        *(mutex->lockers.tqh_last) = thread;

        /* 2) fix up the end pointer for the mutex's list */
        mutex->lockers.tqh_last = cond->waiters.tqh_last;

        if (wakeup) {
                TAILQ_REMOVE(&mutex->lockers, thread, waiting);
                mutex->owner = thread;
                _spinunlock(&mutex->lock);
                __thrwakeup(thread, 1);
        } else
                _spinunlock(&mutex->lock);

        /* 3) reset the condvar's list and mutex pointer */
        TAILQ_INIT(&cond->waiters);
        assert(cond->mutex != NULL);
        cond->mutex = NULL;
        _spinunlock(&cond->lock);

        return (0);
}