| /* | |
| Copyright (c) 2011-2016 mingw-w64 project | |
| Permission is hereby granted, free of charge, to any person obtaining a | |
| copy of this software and associated documentation files (the "Software"), | |
| to deal in the Software without restriction, including without limitation | |
| the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
| and/or sell copies of the Software, and to permit persons to whom the | |
| Software is furnished to do so, subject to the following conditions: | |
| The above copyright notice and this permission notice shall be included in | |
| all copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
| FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
| DEALINGS IN THE SOFTWARE. | |
| */ | |
| #include <windows.h> | |
| #include <stdio.h> | |
| #include <malloc.h> | |
| #include "pthread.h" | |
| #include "thread.h" | |
| #include "ref.h" | |
| #include "rwlock.h" | |
| #include "misc.h" | |
| static pthread_spinlock_t rwl_global = PTHREAD_SPINLOCK_INITIALIZER; | |
| static WINPTHREADS_ATTRIBUTE((noinline)) int rwlock_static_init(pthread_rwlock_t *rw); | |
| static WINPTHREADS_ATTRIBUTE((noinline)) int rwl_unref(volatile pthread_rwlock_t *rwl, int res) | |
| { | |
| pthread_spin_lock(&rwl_global); | |
| #ifdef WINPTHREAD_DBG | |
| assert((((rwlock_t *)*rwl)->valid == LIFE_RWLOCK) && (((rwlock_t *)*rwl)->busy > 0)); | |
| #endif | |
| ((rwlock_t *)*rwl)->busy--; | |
| pthread_spin_unlock(&rwl_global); | |
| return res; | |
| } | |
| static WINPTHREADS_ATTRIBUTE((noinline)) int rwl_ref(pthread_rwlock_t *rwl, int f ) | |
| { | |
| int r = 0; | |
| INIT_RWLOCK(rwl); | |
| pthread_spin_lock(&rwl_global); | |
| if (!rwl || !*rwl || ((rwlock_t *)*rwl)->valid != LIFE_RWLOCK) r = EINVAL; | |
| else { | |
| ((rwlock_t *)*rwl)->busy ++; | |
| } | |
| pthread_spin_unlock(&rwl_global); | |
| return r; | |
| } | |
| static WINPTHREADS_ATTRIBUTE((noinline)) int rwl_ref_unlock(pthread_rwlock_t *rwl ) | |
| { | |
| int r = 0; | |
| pthread_spin_lock(&rwl_global); | |
| if (!rwl || !*rwl || ((rwlock_t *)*rwl)->valid != LIFE_RWLOCK) r = EINVAL; | |
| else if (STATIC_RWL_INITIALIZER(*rwl)) r= EPERM; | |
| else { | |
| ((rwlock_t *)*rwl)->busy ++; | |
| } | |
| pthread_spin_unlock(&rwl_global); | |
| return r; | |
| } | |
| static WINPTHREADS_ATTRIBUTE((noinline)) int rwl_ref_destroy(pthread_rwlock_t *rwl, pthread_rwlock_t *rDestroy ) | |
| { | |
| int r = 0; | |
| *rDestroy = (pthread_rwlock_t)NULL; | |
| pthread_spin_lock(&rwl_global); | |
| if (!rwl || !*rwl) r = EINVAL; | |
| else { | |
| rwlock_t *r_ = (rwlock_t *)*rwl; | |
| if (STATIC_RWL_INITIALIZER(*rwl)) *rwl = (pthread_rwlock_t)NULL; | |
| else if (r_->valid != LIFE_RWLOCK) r = EINVAL; | |
| else if (r_->busy) r = EBUSY; | |
| else { | |
| *rDestroy = *rwl; | |
| *rwl = (pthread_rwlock_t)NULL; | |
| } | |
| } | |
| pthread_spin_unlock(&rwl_global); | |
| return r; | |
| } | |
| static int rwlock_gain_both_locks(rwlock_t *rwlock) | |
| { | |
| int ret; | |
| ret = pthread_mutex_lock(&rwlock->mex); | |
| if (ret != 0) | |
| return ret; | |
| ret = pthread_mutex_lock(&rwlock->mcomplete); | |
| if (ret != 0) | |
| pthread_mutex_unlock(&rwlock->mex); | |
| return ret; | |
| } | |
| static int rwlock_free_both_locks(rwlock_t *rwlock, int last_fail) | |
| { | |
| int ret, ret2; | |
| ret = pthread_mutex_unlock(&rwlock->mcomplete); | |
| ret2 = pthread_mutex_unlock(&rwlock->mex); | |
| if (last_fail && ret2 != 0) | |
| ret = ret2; | |
| else if (!last_fail && !ret) | |
| ret = ret2; | |
| return ret; | |
| } | |
| #ifdef WINPTHREAD_DBG | |
| static int print_state = 0; | |
| void rwl_print_set(int state) | |
| { | |
| print_state = state; | |
| } | |
| void rwl_print(volatile pthread_rwlock_t *rwl, char *txt) | |
| { | |
| if (!print_state) return; | |
| rwlock_t *r = (rwlock_t *)*rwl; | |
| if (r == NULL) { | |
| printf("RWL%p %d %s\n",(void *)*rwl,(int)GetCurrentThreadId(),txt); | |
| } else { | |
| printf("RWL%p %d V=%0X B=%d r=%ld w=%ld L=%p %s\n", | |
| (void *)*rwl, | |
| (int)GetCurrentThreadId(), | |
| (int)r->valid, | |
| (int)r->busy, | |
| 0L,0L,NULL,txt); | |
| } | |
| } | |
| #endif | |
| static pthread_spinlock_t cond_locked = PTHREAD_SPINLOCK_INITIALIZER; | |
| static WINPTHREADS_ATTRIBUTE((noinline)) int rwlock_static_init(pthread_rwlock_t *rw) | |
| { | |
| int r; | |
| pthread_spin_lock(&cond_locked); | |
| if (*rw != PTHREAD_RWLOCK_INITIALIZER) | |
| { | |
| pthread_spin_unlock(&cond_locked); | |
| return EINVAL; | |
| } | |
| r = pthread_rwlock_init (rw, NULL); | |
| pthread_spin_unlock(&cond_locked); | |
| return r; | |
| } | |
| int pthread_rwlock_init (pthread_rwlock_t *rwlock_, const pthread_rwlockattr_t *attr) | |
| { | |
| rwlock_t *rwlock; | |
| int r; | |
| if(!rwlock_) | |
| return EINVAL; | |
| *rwlock_ = (pthread_rwlock_t)NULL; | |
| if ((rwlock = calloc(1, sizeof(*rwlock))) == NULL) | |
| return ENOMEM; | |
| rwlock->valid = DEAD_RWLOCK; | |
| rwlock->nex_count = rwlock->nsh_count = rwlock->ncomplete = 0; | |
| if ((r = pthread_mutex_init (&rwlock->mex, NULL)) != 0) | |
| { | |
| free(rwlock); | |
| return r; | |
| } | |
| if ((r = pthread_mutex_init (&rwlock->mcomplete, NULL)) != 0) | |
| { | |
| pthread_mutex_destroy(&rwlock->mex); | |
| free(rwlock); | |
| return r; | |
| } | |
| if ((r = pthread_cond_init (&rwlock->ccomplete, NULL)) != 0) | |
| { | |
| pthread_mutex_destroy(&rwlock->mex); | |
| pthread_mutex_destroy (&rwlock->mcomplete); | |
| free(rwlock); | |
| return r; | |
| } | |
| rwlock->valid = LIFE_RWLOCK; | |
| *rwlock_ = (pthread_rwlock_t)rwlock; | |
| return r; | |
| } | |
| int pthread_rwlock_destroy (pthread_rwlock_t *rwlock_) | |
| { | |
| rwlock_t *rwlock; | |
| pthread_rwlock_t rDestroy; | |
| int r, r2; | |
| pthread_spin_lock(&cond_locked); | |
| r = rwl_ref_destroy(rwlock_,&rDestroy); | |
| pthread_spin_unlock(&cond_locked); | |
| if(r) return r; | |
| if(!rDestroy) return 0; /* destroyed a (still) static initialized rwl */ | |
| rwlock = (rwlock_t *)rDestroy; | |
| r = rwlock_gain_both_locks (rwlock); | |
| if (r != 0) | |
| { | |
| *rwlock_ = rDestroy; | |
| return r; | |
| } | |
| if (rwlock->nsh_count > rwlock->ncomplete || rwlock->nex_count > 0) | |
| { | |
| *rwlock_ = rDestroy; | |
| r = rwlock_free_both_locks(rwlock, 1); | |
| if (!r) | |
| r = EBUSY; | |
| return r; | |
| } | |
| rwlock->valid = DEAD_RWLOCK; | |
| r = rwlock_free_both_locks(rwlock, 0); | |
| if (r != 0) { *rwlock_ = rDestroy; return r; } | |
| r = pthread_cond_destroy(&rwlock->ccomplete); | |
| r2 = pthread_mutex_destroy(&rwlock->mex); | |
| if (!r) r = r2; | |
| r2 = pthread_mutex_destroy(&rwlock->mcomplete); | |
| if (!r) r = r2; | |
| rwlock->valid = DEAD_RWLOCK; | |
| free((void *)rDestroy); | |
| return 0; | |
| } | |
| int pthread_rwlock_rdlock (pthread_rwlock_t *rwlock_) | |
| { | |
| rwlock_t *rwlock; | |
| int ret; | |
| /* pthread_testcancel(); */ | |
| ret = rwl_ref(rwlock_,0); | |
| if(ret != 0) return ret; | |
| rwlock = (rwlock_t *)*rwlock_; | |
| ret = pthread_mutex_lock(&rwlock->mex); | |
| if (ret != 0) return rwl_unref(rwlock_, ret); | |
| InterlockedIncrement((long*)&rwlock->nsh_count); | |
| if (rwlock->nsh_count == INT_MAX) | |
| { | |
| ret = pthread_mutex_lock(&rwlock->mcomplete); | |
| if (ret != 0) | |
| { | |
| pthread_mutex_unlock(&rwlock->mex); | |
| return rwl_unref(rwlock_,ret); | |
| } | |
| rwlock->nsh_count -= rwlock->ncomplete; | |
| rwlock->ncomplete = 0; | |
| ret = rwlock_free_both_locks(rwlock, 0); | |
| return rwl_unref(rwlock_, ret); | |
| } | |
| ret = pthread_mutex_unlock(&rwlock->mex); | |
| return rwl_unref(rwlock_, ret); | |
| } | |
| int pthread_rwlock_timedrdlock (pthread_rwlock_t *rwlock_, const struct timespec *ts) | |
| { | |
| rwlock_t *rwlock; | |
| int ret; | |
| /* pthread_testcancel(); */ | |
| ret = rwl_ref(rwlock_,0); | |
| if(ret != 0) return ret; | |
| rwlock = (rwlock_t *)*rwlock_; | |
| if ((ret = pthread_mutex_timedlock (&rwlock->mex, ts)) != 0) | |
| return rwl_unref(rwlock_, ret); | |
| InterlockedIncrement(&rwlock->nsh_count); | |
| if (rwlock->nsh_count == INT_MAX) | |
| { | |
| ret = pthread_mutex_timedlock(&rwlock->mcomplete, ts); | |
| if (ret != 0) | |
| { | |
| if (ret == ETIMEDOUT) | |
| InterlockedIncrement(&rwlock->ncomplete); | |
| pthread_mutex_unlock(&rwlock->mex); | |
| return rwl_unref(rwlock_, ret); | |
| } | |
| rwlock->nsh_count -= rwlock->ncomplete; | |
| rwlock->ncomplete = 0; | |
| ret = rwlock_free_both_locks(rwlock, 0); | |
| return rwl_unref(rwlock_, ret); | |
| } | |
| ret = pthread_mutex_unlock(&rwlock->mex); | |
| return rwl_unref(rwlock_, ret); | |
| } | |
| int pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock_) | |
| { | |
| rwlock_t *rwlock; | |
| int ret; | |
| ret = rwl_ref(rwlock_,RWL_TRY); | |
| if(ret != 0) return ret; | |
| rwlock = (rwlock_t *)*rwlock_; | |
| ret = pthread_mutex_trylock(&rwlock->mex); | |
| if (ret != 0) | |
| return rwl_unref(rwlock_, ret); | |
| InterlockedIncrement(&rwlock->nsh_count); | |
| if (rwlock->nsh_count == INT_MAX) | |
| { | |
| ret = pthread_mutex_lock(&rwlock->mcomplete); | |
| if (ret != 0) | |
| { | |
| pthread_mutex_unlock(&rwlock->mex); | |
| return rwl_unref(rwlock_, ret); | |
| } | |
| rwlock->nsh_count -= rwlock->ncomplete; | |
| rwlock->ncomplete = 0; | |
| ret = rwlock_free_both_locks(rwlock, 0); | |
| return rwl_unref(rwlock_, ret); | |
| } | |
| ret = pthread_mutex_unlock(&rwlock->mex); | |
| return rwl_unref(rwlock_,ret); | |
| } | |
| int pthread_rwlock_trywrlock (pthread_rwlock_t *rwlock_) | |
| { | |
| rwlock_t *rwlock; | |
| int ret; | |
| ret = rwl_ref(rwlock_,RWL_TRY); | |
| if(ret != 0) return ret; | |
| rwlock = (rwlock_t *)*rwlock_; | |
| ret = pthread_mutex_trylock (&rwlock->mex); | |
| if (ret != 0) | |
| return rwl_unref(rwlock_, ret); | |
| ret = pthread_mutex_trylock(&rwlock->mcomplete); | |
| if (ret != 0) | |
| { | |
| int r1 = pthread_mutex_unlock(&rwlock->mex); | |
| if (r1 != 0) | |
| ret = r1; | |
| return rwl_unref(rwlock_, ret); | |
| } | |
| if (rwlock->nex_count != 0) | |
| return rwl_unref(rwlock_, EBUSY); | |
| if (rwlock->ncomplete > 0) | |
| { | |
| rwlock->nsh_count -= rwlock->ncomplete; | |
| rwlock->ncomplete = 0; | |
| } | |
| if (rwlock->nsh_count > 0) | |
| { | |
| ret = rwlock_free_both_locks(rwlock, 0); | |
| if (!ret) | |
| ret = EBUSY; | |
| return rwl_unref(rwlock_, ret); | |
| } | |
| rwlock->nex_count = 1; | |
| return rwl_unref(rwlock_, 0); | |
| } | |
| int pthread_rwlock_unlock (pthread_rwlock_t *rwlock_) | |
| { | |
| rwlock_t *rwlock; | |
| int ret; | |
| ret = rwl_ref_unlock(rwlock_); | |
| if(ret != 0) return ret; | |
| rwlock = (rwlock_t *)*rwlock_; | |
| if (rwlock->nex_count == 0) | |
| { | |
| ret = pthread_mutex_lock(&rwlock->mcomplete); | |
| if (!ret) | |
| { | |
| int r1; | |
| InterlockedIncrement(&rwlock->ncomplete); | |
| if (rwlock->ncomplete == 0) | |
| ret = pthread_cond_signal(&rwlock->ccomplete); | |
| r1 = pthread_mutex_unlock(&rwlock->mcomplete); | |
| if (!ret) | |
| ret = r1; | |
| } | |
| } | |
| else | |
| { | |
| InterlockedDecrement(&rwlock->nex_count); | |
| ret = rwlock_free_both_locks(rwlock, 0); | |
| } | |
| return rwl_unref(rwlock_, ret); | |
| } | |
| static void st_cancelwrite (void *arg) | |
| { | |
| rwlock_t *rwlock = (rwlock_t *)arg; | |
| rwlock->nsh_count = - rwlock->ncomplete; | |
| rwlock->ncomplete = 0; | |
| rwlock_free_both_locks(rwlock, 0); | |
| } | |
| int pthread_rwlock_wrlock (pthread_rwlock_t *rwlock_) | |
| { | |
| rwlock_t *rwlock; | |
| int ret; | |
| /* pthread_testcancel(); */ | |
| ret = rwl_ref(rwlock_,0); | |
| if(ret != 0) return ret; | |
| rwlock = (rwlock_t *)*rwlock_; | |
| ret = rwlock_gain_both_locks(rwlock); | |
| if (ret != 0) | |
| return rwl_unref(rwlock_,ret); | |
| if (rwlock->nex_count == 0) | |
| { | |
| if (rwlock->ncomplete > 0) | |
| { | |
| rwlock->nsh_count -= rwlock->ncomplete; | |
| rwlock->ncomplete = 0; | |
| } | |
| if (rwlock->nsh_count > 0) | |
| { | |
| rwlock->ncomplete = -rwlock->nsh_count; | |
| pthread_cleanup_push(st_cancelwrite, (void *) rwlock); | |
| do { | |
| ret = pthread_cond_wait(&rwlock->ccomplete, &rwlock->mcomplete); | |
| } while (!ret && rwlock->ncomplete < 0); | |
| pthread_cleanup_pop(!ret ? 0 : 1); | |
| if (!ret) | |
| rwlock->nsh_count = 0; | |
| } | |
| } | |
| if(!ret) | |
| InterlockedIncrement((long*)&rwlock->nex_count); | |
| return rwl_unref(rwlock_,ret); | |
| } | |
| int pthread_rwlock_timedwrlock (pthread_rwlock_t *rwlock_, const struct timespec *ts) | |
| { | |
| int ret; | |
| rwlock_t *rwlock; | |
| /* pthread_testcancel(); */ | |
| if (!rwlock_ || !ts) | |
| return EINVAL; | |
| if ((ret = rwl_ref(rwlock_,0)) != 0) | |
| return ret; | |
| rwlock = (rwlock_t *)*rwlock_; | |
| ret = pthread_mutex_timedlock(&rwlock->mex, ts); | |
| if (ret != 0) | |
| return rwl_unref(rwlock_,ret); | |
| ret = pthread_mutex_timedlock (&rwlock->mcomplete, ts); | |
| if (ret != 0) | |
| { | |
| pthread_mutex_unlock(&rwlock->mex); | |
| return rwl_unref(rwlock_,ret); | |
| } | |
| if (rwlock->nex_count == 0) | |
| { | |
| if (rwlock->ncomplete > 0) | |
| { | |
| rwlock->nsh_count -= rwlock->ncomplete; | |
| rwlock->ncomplete = 0; | |
| } | |
| if (rwlock->nsh_count > 0) | |
| { | |
| rwlock->ncomplete = -rwlock->nsh_count; | |
| pthread_cleanup_push(st_cancelwrite, (void *) rwlock); | |
| do { | |
| ret = pthread_cond_timedwait(&rwlock->ccomplete, &rwlock->mcomplete, ts); | |
| } while (rwlock->ncomplete < 0 && !ret); | |
| pthread_cleanup_pop(!ret ? 0 : 1); | |
| if (!ret) | |
| rwlock->nsh_count = 0; | |
| } | |
| } | |
| if(!ret) | |
| InterlockedIncrement((long*)&rwlock->nex_count); | |
| return rwl_unref(rwlock_,ret); | |
| } | |
| int pthread_rwlockattr_destroy(pthread_rwlockattr_t *a) | |
| { | |
| if (!a) | |
| return EINVAL; | |
| return 0; | |
| } | |
| int pthread_rwlockattr_init(pthread_rwlockattr_t *a) | |
| { | |
| if (!a) | |
| return EINVAL; | |
| *a = PTHREAD_PROCESS_PRIVATE; | |
| return 0; | |
| } | |
| int pthread_rwlockattr_getpshared(pthread_rwlockattr_t *a, int *s) | |
| { | |
| if (!a || !s) | |
| return EINVAL; | |
| *s = *a; | |
| return 0; | |
| } | |
| int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *a, int s) | |
| { | |
| if (!a || (s != PTHREAD_PROCESS_SHARED && s != PTHREAD_PROCESS_PRIVATE)) | |
| return EINVAL; | |
| *a = s; | |
| return 0; | |
| } |