pcloudcc-lneely/pclsync/plocks.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;
}