/* * rwlock.c * * Description: * This translation unit implements read/write lock primitives. * * Pthreads-win32 - POSIX Threads Library for Win32 * Copyright (C) 1998 * * 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 */ #include #include "pthread.h" #include "implement.h" static int _rwlock_check_need_init(pthread_rwlock_t *rwlock) { int result = 0; /* * The following guarded test is specifically for statically * initialised rwlocks (via PTHREAD_RWLOCK_INITIALIZER). * * Note that by not providing this synchronisation we risk * introducing race conditions into applications which are * correctly written. * * Approach * -------- * We know that static rwlocks will not be PROCESS_SHARED * so we can serialise access to internal state using * Win32 Critical Sections rather than Win32 Mutexes. * * If using a single global lock slows applications down too much, * multiple global locks could be created and hashed on some random * value associated with each mutex, the pointer perhaps. At a guess, * a good value for the optimal number of global locks might be * the number of processors + 1. * */ EnterCriticalSection(&_pthread_rwlock_test_init_lock); /* * We got here possibly under race * conditions. Check again inside the critical section * and only initialise if the rwlock is valid (not been destroyed). * If a static rwlock has been destroyed, the application can * re-initialise it only by calling pthread_rwlock_init() * explicitly. */ if (*rwlock == (pthread_rwlock_t) _PTHREAD_OBJECT_AUTO_INIT) { result = pthread_rwlock_init(rwlock, NULL); } else if (*rwlock == NULL) { /* * The rwlock has been destroyed while we were waiting to * initialise it, so the operation that caused the * auto-initialisation should fail. */ result = EINVAL; } LeaveCriticalSection(&_pthread_rwlock_test_init_lock); return(result); } int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr) { int result = 0; pthread_rwlock_t rw; if (rwlock == NULL) { return EINVAL; } rw = *rwlock; rw = (pthread_rwlock_t) calloc(1, sizeof(*rw)); if (rw == NULL) { result = ENOMEM; goto FAIL0; } if (attr != NULL && *attr != NULL) { result = EINVAL; /* Not supported */ goto FAIL0; } if ((result = pthread_mutex_init(&(rw->rw_lock), NULL)) != 0) { goto FAIL1; } if ((result = pthread_cond_init(&(rw->rw_condreaders), NULL)) != 0) { goto FAIL2; } if ((result = pthread_cond_init(&(rw->rw_condwriters), NULL)) != 0) { goto FAIL3; } rw->rw_nwaitreaders = 0; rw->rw_nwaitwriters = 0; rw->rw_refcount = 0; rw->rw_magic = RW_MAGIC; result = 0; goto FAIL0; FAIL3: (void) pthread_cond_destroy(&(rw->rw_condreaders)); FAIL2: (void) pthread_mutex_destroy(&(rw->rw_lock)); FAIL1: FAIL0: *rwlock = rw; return(result); } int pthread_rwlock_destroy(pthread_rwlock_t *rwlock) { pthread_rwlock_t rw; int result = 0; if (rwlock == NULL || *rwlock == NULL) { return(EINVAL); } if (*rwlock != (pthread_rwlock_t) _PTHREAD_OBJECT_AUTO_INIT) { rw = *rwlock; if (pthread_mutex_lock(&(rw->rw_lock)) != 0) { return(EINVAL); } if (rw->rw_magic != RW_MAGIC) { (void) pthread_mutex_unlock(&(rw->rw_lock)); return(EINVAL); } if (rw->rw_refcount != 0 || rw->rw_nwaitreaders != 0 || rw->rw_nwaitwriters != 0) { (void) pthread_mutex_unlock(&(rw->rw_lock)); result = EBUSY; } else { rw->rw_magic = 0; (void) pthread_mutex_unlock(&(rw->rw_lock)); (void) pthread_cond_destroy(&(rw->rw_condreaders)); (void) pthread_cond_destroy(&(rw->rw_condwriters)); (void) pthread_mutex_destroy(&(rw->rw_lock)); free(rw); *rwlock = NULL; } } else { /* * See notes in _rwlock_check_need_init() above also. */ EnterCriticalSection(&_pthread_rwlock_test_init_lock); /* * Check again. */ if (*rwlock == (pthread_rwlock_t) _PTHREAD_OBJECT_AUTO_INIT) { /* * This is all we need to do to destroy a statically * initialised rwlock that has not yet been used (initialised). * If we get to here, another thread * waiting to initialise this rwlock will get an EINVAL. */ *rwlock = NULL; } else { /* * The rwlock has been initialised while we were waiting * so assume it's in use. */ result = EBUSY; } LeaveCriticalSection(&_pthread_rwlock_test_init_lock); } return(result); } static void _rwlock_cancelrdwait(void * arg) { pthread_rwlock_t rw; rw = (pthread_rwlock_t) arg; rw->rw_nwaitreaders--; pthread_mutex_unlock(&(rw->rw_lock)); } int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock) { int result = 0; pthread_rwlock_t rw; if (rwlock == NULL || *rwlock == NULL) { return(EINVAL); } /* * We do a quick check to see if we need to do more work * to initialise a static rwlock. We check * again inside the guarded section of _rwlock_check_need_init() * to avoid race conditions. */ if (*rwlock == (pthread_rwlock_t) _PTHREAD_OBJECT_AUTO_INIT) { result = _rwlock_check_need_init(rwlock); if (result != 0 && result != EBUSY) { return(result); } } rw = *rwlock; if ((result = pthread_mutex_lock(&(rw->rw_lock))) != 0) { return(result); } if (rw->rw_magic != RW_MAGIC) { result = EINVAL; goto FAIL1; } /* * Give preference to waiting writers */ while (rw->rw_refcount < 0 || rw->rw_nwaitwriters > 0) { rw->rw_nwaitreaders++; pthread_cleanup_push(_rwlock_cancelrdwait, rw); result = pthread_cond_wait(&(rw->rw_condreaders), &(rw->rw_lock)); pthread_cleanup_pop(0); rw->rw_nwaitreaders--; if (result != 0) { break; } } if (result == 0) { rw->rw_refcount++; /* another reader has a read lock */ } FAIL1: (void) pthread_mutex_unlock(&(rw->rw_lock)); return(result); } static void _rwlock_cancelwrwait(void * arg) { pthread_rwlock_t rw; rw = (pthread_rwlock_t) arg; rw->rw_nwaitwriters--; (void) pthread_mutex_unlock(&(rw->rw_lock)); } int pthread_rwlock_wrlock(pthread_rwlock_t * rwlock) { int result; pthread_rwlock_t rw; if (rwlock == NULL || *rwlock == NULL) { return(EINVAL); } /* * We do a quick check to see if we need to do more work * to initialise a static rwlock. We check * again inside the guarded section of _rwlock_check_need_init() * to avoid race conditions. */ if (*rwlock == (pthread_rwlock_t) _PTHREAD_OBJECT_AUTO_INIT) { result = _rwlock_check_need_init(rwlock); if (result != 0 && result != EBUSY) { return(result); } } rw = *rwlock; if (rw->rw_magic != RW_MAGIC) { return(EINVAL); } if ((result = pthread_mutex_lock(&(rw->rw_lock))) != 0) { return(result); } while (rw->rw_refcount != 0) { rw->rw_nwaitwriters++; pthread_cleanup_push(_rwlock_cancelwrwait, rw); result = pthread_cond_wait(&(rw->rw_condwriters), &(rw->rw_lock)); pthread_cleanup_pop(0); rw->rw_nwaitwriters--; if (result != 0) { break; } } if (result == 0) { rw->rw_refcount = -1; } (void) pthread_mutex_unlock(&(rw->rw_lock)); return(result); } int pthread_rwlock_unlock(pthread_rwlock_t * rwlock) { int result = 0; pthread_rwlock_t rw; if (rwlock == NULL || *rwlock == NULL) { return(EINVAL); } if (*rwlock == (pthread_rwlock_t) _PTHREAD_OBJECT_AUTO_INIT) { /* * Assume any race condition here is harmless. */ return(0); } rw = *rwlock; if ((result = pthread_mutex_lock(&(rw->rw_lock))) != 0) { return(result); } if (rw->rw_magic != RW_MAGIC) { result = EINVAL; goto FAIL1; } if (rw->rw_refcount > 0) { rw->rw_refcount--; /* releasing a reader */ } else if (rw->rw_refcount == -1) { rw->rw_refcount = 0; /* releasing a writer */ } else { return(EINVAL); } result = 0; /* * Give preference to waiting writers over waiting readers */ if (rw->rw_nwaitwriters > 0) { if (rw->rw_refcount == 0) { result = pthread_cond_signal(&(rw->rw_condwriters)); } } else if (rw->rw_nwaitreaders > 0) { result = pthread_cond_broadcast(&(rw->rw_condreaders)); } FAIL1: (void) pthread_mutex_unlock(&(rw->rw_lock)); return(result); } int pthread_rwlock_tryrdlock(pthread_rwlock_t * rwlock) { int result = 0; pthread_rwlock_t rw; if (rwlock == NULL || *rwlock == NULL) { return(EINVAL); } /* * We do a quick check to see if we need to do more work * to initialise a static rwlock. We check * again inside the guarded section of _rwlock_check_need_init() * to avoid race conditions. */ if (*rwlock == (pthread_rwlock_t) _PTHREAD_OBJECT_AUTO_INIT) { result = _rwlock_check_need_init(rwlock); if (result != 0 && result != EBUSY) { return(result); } } rw = *rwlock; if ((result = pthread_mutex_lock(&(rw->rw_lock))) != 0) { return(result); } if (rw->rw_magic != RW_MAGIC) { result = EINVAL; goto FAIL1; } if (rw->rw_refcount == -1 || rw->rw_nwaitwriters > 0) { result = EBUSY; /* held by a writer or waiting writers */ } else { rw->rw_refcount++; /* increment count of reader locks */ } FAIL1: (void) pthread_mutex_unlock(&(rw->rw_lock)); return(result); } int pthread_rwlock_trywrlock(pthread_rwlock_t * rwlock) { int result = 0; pthread_rwlock_t rw; if (rwlock == NULL || *rwlock == NULL) { return(EINVAL); } /* * We do a quick check to see if we need to do more work * to initialise a static rwlock. We check * again inside the guarded section of _rwlock_check_need_init() * to avoid race conditions. */ if (*rwlock == (pthread_rwlock_t) _PTHREAD_OBJECT_AUTO_INIT) { result = _rwlock_check_need_init(rwlock); if (result != 0 && result != EBUSY) { return(result); } } rw = *rwlock; if ((result = pthread_mutex_lock(&(rw->rw_lock))) != 0) { return(result); } if (rw->rw_magic != RW_MAGIC) { result = EINVAL; goto FAIL1; } if (rw->rw_refcount != 0) { result = EBUSY; /* held by either writer or reader(s) */ } else { rw->rw_refcount = -1; /* available, indicate a writer has it */ } FAIL1: (void) pthread_mutex_unlock(&(rw->rw_lock)); return(result); }