| /* |
| 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; |
| } |