475 lines
14 KiB
C
475 lines
14 KiB
C
/*
|
|
Copyright (c) 2015 Anton Titov.
|
|
|
|
Copyright (c) 2015 pCloud Ltd. All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions
|
|
are met: Redistributions of source code must retain the above
|
|
copyright notice, this list of conditions and the following
|
|
disclaimer. Redistributions in binary form must reproduce the
|
|
above copyright notice, this list of conditions and the following
|
|
disclaimer in the documentation and/or other materials provided
|
|
with the distribution. Neither the name of pCloud Ltd nor the
|
|
names of its contributors may be used to endorse or promote
|
|
products derived from this software without specific prior written
|
|
permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL pCloud
|
|
Ltd BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
|
DAMAGE.
|
|
*/
|
|
|
|
#include "plocks.h"
|
|
#include "plibs.h"
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
|
|
#define PSYNC_RW_OPT_PREFER_READ 1U
|
|
#define PSYNC_RW_OPT_RESERVED 2U
|
|
|
|
#if defined(_WIN64) || defined(__x86_64__)
|
|
typedef uint32_t uint_halfptr_t;
|
|
#else
|
|
typedef uint16_t uint_halfptr_t;
|
|
#endif
|
|
|
|
#define PSYNC_WR_RESERVED ((uint_halfptr_t)-1)
|
|
|
|
typedef union {
|
|
void *ptr;
|
|
uint_halfptr_t cnt[2];
|
|
} psync_rwlock_lockcnt_t;
|
|
|
|
void plocks_init(psync_rwlock_t *rw) {
|
|
pdbg_assert(sizeof(void *) == sizeof(psync_rwlock_lockcnt_t));
|
|
rw->rcount = 0;
|
|
rw->rwait = 0;
|
|
rw->wcount = 0;
|
|
rw->wwait = 0;
|
|
rw->opts = 0;
|
|
pthread_key_create(&rw->cntkey, NULL);
|
|
pthread_mutex_init(&rw->mutex, NULL);
|
|
pthread_cond_init(&rw->rcond, NULL);
|
|
pthread_cond_init(&rw->wcond, NULL);
|
|
}
|
|
|
|
void plocks_destroy(psync_rwlock_t *rw) {
|
|
pthread_key_delete(rw->cntkey);
|
|
pthread_mutex_destroy(&rw->mutex);
|
|
pthread_cond_destroy(&rw->rcond);
|
|
pthread_cond_destroy(&rw->wcond);
|
|
}
|
|
|
|
static psync_rwlock_lockcnt_t psync_rwlock_get_count(psync_rwlock_t *rw) {
|
|
psync_rwlock_lockcnt_t locks;
|
|
locks.ptr = pthread_getspecific(rw->cntkey);
|
|
return locks;
|
|
}
|
|
|
|
static void psync_rwlock_set_count(psync_rwlock_t *rw,
|
|
psync_rwlock_lockcnt_t cnt) {
|
|
pthread_setspecific(rw->cntkey, cnt.ptr);
|
|
}
|
|
|
|
static int psync_rwlock_check_rdrecursive_in(psync_rwlock_t *rw) {
|
|
psync_rwlock_lockcnt_t cnt;
|
|
cnt = psync_rwlock_get_count(rw);
|
|
if (cnt.cnt[0]) {
|
|
cnt.cnt[0]++;
|
|
psync_rwlock_set_count(rw, cnt);
|
|
return 1;
|
|
} else if (cnt.cnt[1]) {
|
|
pdbg_assert(cnt.cnt[1] != PSYNC_WR_RESERVED);
|
|
cnt.cnt[1]++;
|
|
psync_rwlock_set_count(rw, cnt);
|
|
return 1;
|
|
} else
|
|
return 0;
|
|
}
|
|
|
|
static int psync_rwlock_check_wrrecursive_in(psync_rwlock_t *rw) {
|
|
psync_rwlock_lockcnt_t cnt;
|
|
cnt = psync_rwlock_get_count(rw);
|
|
pdbg_assert(!cnt.cnt[0]);
|
|
if (cnt.cnt[1]) {
|
|
pdbg_assert(cnt.cnt[1] != PSYNC_WR_RESERVED);
|
|
cnt.cnt[1]++;
|
|
psync_rwlock_set_count(rw, cnt);
|
|
return 1;
|
|
} else
|
|
return 0;
|
|
}
|
|
|
|
static int psync_rwlock_check_recursive_out(psync_rwlock_t *rw) {
|
|
psync_rwlock_lockcnt_t cnt;
|
|
cnt = psync_rwlock_get_count(rw);
|
|
if (cnt.cnt[0]) {
|
|
cnt.cnt[0]--;
|
|
psync_rwlock_set_count(rw, cnt);
|
|
return cnt.cnt[0] > 0;
|
|
} else {
|
|
pdbg_assert(cnt.cnt[1]);
|
|
cnt.cnt[1]--;
|
|
psync_rwlock_set_count(rw, cnt);
|
|
return cnt.cnt[1] > 0;
|
|
}
|
|
}
|
|
|
|
static int psync_rwlock_is_reserved_by_this_thread_clr(psync_rwlock_t *rw) {
|
|
psync_rwlock_lockcnt_t cnt;
|
|
cnt = psync_rwlock_get_count(rw);
|
|
if (cnt.cnt[1] == PSYNC_WR_RESERVED) {
|
|
cnt.cnt[1] = 0;
|
|
psync_rwlock_set_count(rw, cnt);
|
|
return 1;
|
|
} else
|
|
return 0;
|
|
}
|
|
|
|
static psync_rwlock_lockcnt_t psync_rwlock_create_cnt(uint_halfptr_t rd,
|
|
uint_halfptr_t wr) {
|
|
psync_rwlock_lockcnt_t cnt;
|
|
cnt.cnt[0] = rd;
|
|
cnt.cnt[1] = wr;
|
|
return cnt;
|
|
}
|
|
|
|
void plocks_rdlock(psync_rwlock_t *rw) {
|
|
struct timespec ts;
|
|
int ret;
|
|
if (psync_rwlock_check_rdrecursive_in(rw))
|
|
return;
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
ts.tv_sec += 30;
|
|
ret = pthread_mutex_timedlock(&rw->mutex, &ts);
|
|
if (ret == ETIMEDOUT) {
|
|
pdbg_logf(D_CRITICAL, "pthread_mutex_timedlock timeout on rdlock");
|
|
abort();
|
|
}
|
|
while (rw->wcount || (rw->wwait && !(rw->opts & PSYNC_RW_OPT_RESERVED))) {
|
|
rw->rwait++;
|
|
pthread_cond_wait(&rw->rcond, &rw->mutex);
|
|
rw->rwait--;
|
|
}
|
|
rw->rcount++;
|
|
pthread_mutex_unlock(&rw->mutex);
|
|
psync_rwlock_set_count(rw, psync_rwlock_create_cnt(1, 0));
|
|
}
|
|
|
|
int plocks_tryrdlock(psync_rwlock_t *rw) {
|
|
struct timespec ts;
|
|
int ret;
|
|
if (psync_rwlock_check_rdrecursive_in(rw))
|
|
return 0;
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
ts.tv_sec += 30;
|
|
ret = pthread_mutex_timedlock(&rw->mutex, &ts);
|
|
if (ret == ETIMEDOUT) {
|
|
pdbg_logf(D_CRITICAL, "pthread_mutex_timedlock timeout on tryrdlock");
|
|
abort();
|
|
}
|
|
if (rw->wcount || (rw->wwait && !(rw->opts & PSYNC_RW_OPT_RESERVED))) {
|
|
pthread_mutex_unlock(&rw->mutex);
|
|
return -1;
|
|
}
|
|
rw->rcount++;
|
|
pthread_mutex_unlock(&rw->mutex);
|
|
psync_rwlock_set_count(rw, psync_rwlock_create_cnt(1, 0));
|
|
return 0;
|
|
}
|
|
|
|
int plocks_timedrdlock(psync_rwlock_t *rw,
|
|
const struct timespec *abstime) {
|
|
struct timespec ts;
|
|
int ret;
|
|
if (psync_rwlock_check_rdrecursive_in(rw))
|
|
return 0;
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
ts.tv_sec += 30;
|
|
ret = pthread_mutex_timedlock(&rw->mutex, &ts);
|
|
if (ret == ETIMEDOUT) {
|
|
pdbg_logf(D_CRITICAL, "pthread_mutex_timedlock timeout on timedrdlock");
|
|
abort();
|
|
}
|
|
while (rw->wcount || (rw->wwait && !(rw->opts & PSYNC_RW_OPT_RESERVED))) {
|
|
rw->rwait++;
|
|
if (unlikely(pthread_cond_timedwait(&rw->rcond, &rw->mutex, abstime))) {
|
|
rw->rwait--;
|
|
pthread_mutex_unlock(&rw->mutex);
|
|
return -1;
|
|
}
|
|
rw->rwait--;
|
|
}
|
|
rw->rcount++;
|
|
pthread_mutex_unlock(&rw->mutex);
|
|
psync_rwlock_set_count(rw, psync_rwlock_create_cnt(1, 0));
|
|
return 0;
|
|
}
|
|
|
|
void plocks_rdlock_starvewr(psync_rwlock_t *rw) {
|
|
struct timespec ts;
|
|
int ret;
|
|
if (psync_rwlock_check_rdrecursive_in(rw))
|
|
return;
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
ts.tv_sec += 30;
|
|
ret = pthread_mutex_timedlock(&rw->mutex, &ts);
|
|
if (ret == ETIMEDOUT) {
|
|
pdbg_logf(D_CRITICAL, "pthread_mutex_timedlock timeout on rdlock_starvewr");
|
|
abort();
|
|
}
|
|
while (rw->wcount) {
|
|
rw->rwait++;
|
|
rw->opts |= PSYNC_RW_OPT_PREFER_READ;
|
|
pthread_cond_wait(&rw->rcond, &rw->mutex);
|
|
rw->rwait--;
|
|
rw->opts &= ~PSYNC_RW_OPT_PREFER_READ;
|
|
}
|
|
rw->rcount++;
|
|
pthread_mutex_unlock(&rw->mutex);
|
|
psync_rwlock_set_count(rw, psync_rwlock_create_cnt(1, 0));
|
|
}
|
|
|
|
void plocks_wrlock(psync_rwlock_t *rw) {
|
|
struct timespec ts;
|
|
int ret;
|
|
if (psync_rwlock_check_wrrecursive_in(rw))
|
|
return;
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
ts.tv_sec += 30;
|
|
ret = pthread_mutex_timedlock(&rw->mutex, &ts);
|
|
if (ret == ETIMEDOUT) {
|
|
pdbg_logf(D_CRITICAL, "pthread_mutex_timedlock timeout on wrlock");
|
|
abort();
|
|
}
|
|
while (rw->rcount || rw->wcount || (rw->opts & PSYNC_RW_OPT_RESERVED)) {
|
|
rw->wwait++;
|
|
pthread_cond_wait(&rw->wcond, &rw->mutex);
|
|
rw->wwait--;
|
|
}
|
|
rw->wcount++;
|
|
pthread_mutex_unlock(&rw->mutex);
|
|
psync_rwlock_set_count(rw, psync_rwlock_create_cnt(0, 1));
|
|
}
|
|
|
|
int plocks_trywrlock(psync_rwlock_t *rw) {
|
|
struct timespec ts;
|
|
int ret;
|
|
if (psync_rwlock_check_wrrecursive_in(rw))
|
|
return 0;
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
ts.tv_sec += 30;
|
|
ret = pthread_mutex_timedlock(&rw->mutex, &ts);
|
|
if (ret == ETIMEDOUT) {
|
|
pdbg_logf(D_CRITICAL, "pthread_mutex_timedlock timeout on trywrlock");
|
|
abort();
|
|
}
|
|
if (rw->rcount || rw->wcount || (rw->opts & PSYNC_RW_OPT_RESERVED)) {
|
|
pthread_mutex_unlock(&rw->mutex);
|
|
return -1;
|
|
}
|
|
rw->wcount++;
|
|
pthread_mutex_unlock(&rw->mutex);
|
|
psync_rwlock_set_count(rw, psync_rwlock_create_cnt(0, 1));
|
|
return 0;
|
|
}
|
|
|
|
int plocks_timedwrlock(psync_rwlock_t *rw,
|
|
const struct timespec *abstime) {
|
|
struct timespec ts;
|
|
int ret;
|
|
if (psync_rwlock_check_wrrecursive_in(rw))
|
|
return 0;
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
ts.tv_sec += 30;
|
|
ret = pthread_mutex_timedlock(&rw->mutex, &ts);
|
|
if (ret == ETIMEDOUT) {
|
|
pdbg_logf(D_CRITICAL, "pthread_mutex_timedlock timeout on timedwrlock");
|
|
abort();
|
|
}
|
|
while (rw->rcount || rw->wcount || (rw->opts & PSYNC_RW_OPT_RESERVED)) {
|
|
rw->wwait++;
|
|
if (unlikely(pthread_cond_timedwait(&rw->wcond, &rw->mutex, abstime))) {
|
|
if (--rw->wwait == 0 && !rw->wcount && rw->rwait)
|
|
pthread_cond_broadcast(&rw->rcond);
|
|
pthread_mutex_unlock(&rw->mutex);
|
|
return -1;
|
|
}
|
|
rw->wwait--;
|
|
}
|
|
rw->wcount++;
|
|
pthread_mutex_unlock(&rw->mutex);
|
|
psync_rwlock_set_count(rw, psync_rwlock_create_cnt(0, 1));
|
|
return 0;
|
|
}
|
|
|
|
void plocks_rslock(psync_rwlock_t *rw) {
|
|
struct timespec ts;
|
|
int ret;
|
|
psync_rwlock_lockcnt_t cnt;
|
|
cnt = psync_rwlock_get_count(rw);
|
|
pdbg_assert(cnt.cnt[0] == 0);
|
|
if (cnt.cnt[1]) {
|
|
if (cnt.cnt[1] == PSYNC_WR_RESERVED)
|
|
cnt.cnt[0]++;
|
|
else
|
|
cnt.cnt[1]++;
|
|
psync_rwlock_set_count(rw, cnt);
|
|
return;
|
|
}
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
ts.tv_sec += 30;
|
|
ret = pthread_mutex_timedlock(&rw->mutex, &ts);
|
|
if (ret == ETIMEDOUT) {
|
|
pdbg_logf(D_CRITICAL, "pthread_mutex_timedlock timeout on rslock");
|
|
abort();
|
|
}
|
|
while (rw->wcount || (rw->opts & PSYNC_RW_OPT_RESERVED)) {
|
|
rw->wwait++;
|
|
pthread_cond_wait(&rw->wcond, &rw->mutex);
|
|
rw->wwait--;
|
|
}
|
|
if (rw->rwait)
|
|
pthread_cond_broadcast(&rw->rcond);
|
|
rw->rcount++;
|
|
rw->opts |= PSYNC_RW_OPT_RESERVED;
|
|
pthread_mutex_unlock(&rw->mutex);
|
|
cnt.cnt[0] = 1;
|
|
cnt.cnt[1] = PSYNC_WR_RESERVED;
|
|
psync_rwlock_set_count(rw, cnt);
|
|
}
|
|
|
|
int plocks_towrlock(psync_rwlock_t *rw) {
|
|
struct timespec ts;
|
|
int ret;
|
|
psync_rwlock_lockcnt_t cnt;
|
|
cnt = psync_rwlock_get_count(rw);
|
|
if (cnt.cnt[1] && cnt.cnt[1] != PSYNC_WR_RESERVED)
|
|
return 0;
|
|
pdbg_assert(cnt.cnt[0]);
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
ts.tv_sec += 30;
|
|
ret = pthread_mutex_timedlock(&rw->mutex, &ts);
|
|
if (ret == ETIMEDOUT) {
|
|
pdbg_logf(D_CRITICAL, "pthread_mutex_timedlock timeout on towrlock");
|
|
abort();
|
|
}
|
|
pdbg_assert(rw->rcount);
|
|
pdbg_assert(!rw->wcount);
|
|
if (rw->opts & PSYNC_RW_OPT_RESERVED) {
|
|
if (cnt.cnt[1] != PSYNC_WR_RESERVED) {
|
|
pthread_mutex_unlock(&rw->mutex);
|
|
// pdbg_logf(D_NOTICE, "could not upgrade to write lock, consider using
|
|
// reserved locks instead");
|
|
return -1;
|
|
}
|
|
} else
|
|
rw->opts |= PSYNC_RW_OPT_RESERVED;
|
|
rw->rcount--;
|
|
while (rw->rcount) {
|
|
rw->wwait++;
|
|
pthread_cond_wait(&rw->wcond, &rw->mutex);
|
|
rw->wwait--;
|
|
}
|
|
pdbg_assert(!rw->wcount);
|
|
rw->wcount++;
|
|
rw->opts &= ~PSYNC_RW_OPT_RESERVED;
|
|
pthread_mutex_unlock(&rw->mutex);
|
|
cnt.cnt[1] = cnt.cnt[0];
|
|
cnt.cnt[0] = 0;
|
|
psync_rwlock_set_count(rw, cnt);
|
|
return 0;
|
|
}
|
|
|
|
void plocks_unlock(psync_rwlock_t *rw) {
|
|
struct timespec ts;
|
|
int ret;
|
|
if (psync_rwlock_check_recursive_out(rw))
|
|
return;
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
ts.tv_sec += 30;
|
|
ret = pthread_mutex_timedlock(&rw->mutex, &ts);
|
|
if (ret == ETIMEDOUT) {
|
|
pdbg_logf(D_CRITICAL, "pthread_mutex_timedlock timeout on unlock");
|
|
abort();
|
|
}
|
|
pdbg_assert(!(rw->rcount && rw->wcount));
|
|
if (rw->rcount) {
|
|
if ((rw->opts & PSYNC_RW_OPT_RESERVED) &&
|
|
psync_rwlock_is_reserved_by_this_thread_clr(rw))
|
|
rw->opts &= ~PSYNC_RW_OPT_RESERVED;
|
|
if (--rw->rcount == 0) {
|
|
if (rw->wwait) {
|
|
if (rw->opts & PSYNC_RW_OPT_RESERVED)
|
|
pthread_cond_broadcast(&rw->wcond);
|
|
else
|
|
pthread_cond_signal(&rw->wcond);
|
|
}
|
|
}
|
|
} else {
|
|
// pdbg_logf(D_NOTICE, "Releasing write lock.");
|
|
pdbg_assert(rw->wcount);
|
|
if (--rw->wcount == 0) {
|
|
if (rw->opts & PSYNC_RW_OPT_RESERVED) {
|
|
pdbg_assert(rw->wwait);
|
|
// this could create some small thundering herd, but is probably better
|
|
// than using one more condition variable
|
|
pthread_cond_broadcast(&rw->wcond);
|
|
} else if (rw->opts & PSYNC_RW_OPT_PREFER_READ) {
|
|
if (rw->rwait)
|
|
pthread_cond_broadcast(&rw->rcond);
|
|
else if (rw->wwait)
|
|
pthread_cond_signal(&rw->wcond);
|
|
} else {
|
|
if (rw->wwait)
|
|
pthread_cond_signal(&rw->wcond);
|
|
else if (rw->rwait)
|
|
pthread_cond_broadcast(&rw->rcond);
|
|
}
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&rw->mutex);
|
|
}
|
|
|
|
unsigned plocks_num_waiters(psync_rwlock_t *rw) {
|
|
struct timespec ts;
|
|
int ret_lock;
|
|
unsigned ret;
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
ts.tv_sec += 30;
|
|
ret_lock = pthread_mutex_timedlock(&rw->mutex, &ts);
|
|
if (ret_lock == ETIMEDOUT) {
|
|
pdbg_logf(D_CRITICAL, "pthread_mutex_timedlock timeout on num_waiters");
|
|
abort();
|
|
}
|
|
ret = rw->rwait + rw->wwait;
|
|
pthread_mutex_unlock(&rw->mutex);
|
|
return ret;
|
|
}
|
|
|
|
int plocks_holding_rdlock(psync_rwlock_t *rw) {
|
|
return psync_rwlock_get_count(rw).cnt[0] != 0;
|
|
}
|
|
|
|
int plocks_holding_wrlock(psync_rwlock_t *rw) {
|
|
psync_rwlock_lockcnt_t cnt;
|
|
cnt = psync_rwlock_get_count(rw);
|
|
return cnt.cnt[1] != 0 && cnt.cnt[1] != PSYNC_WR_RESERVED;
|
|
}
|
|
|
|
int plocks_holding_lock(psync_rwlock_t *rw) {
|
|
psync_rwlock_lockcnt_t cnt;
|
|
cnt = psync_rwlock_get_count(rw);
|
|
return cnt.cnt[0] != 0 || cnt.cnt[1] != 0;
|
|
}
|