Edit

IABSD.fr/src/lib/libc/thread/rthread_sync.c

Branch :

  • Show log

    Commit

  • Author : claudio
    Date : 2026-03-27 12:26:58
    Hash : ceb7e02c
    Message : Make pthread_mutex_destroy() not error out for mutexes that were initalized with PTHREAD_MUTEX_INITIALIZER but not used. Unify handling of mutexp == NULL in both version of pthread_mutex_destroy() and ensure that *mutexp == NULL is not considered an error. Problem found by tb@ with rpki-client. OK kettenis@ tb@

  • 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);
    }