Commit 55820ab7 authored by Orvid King's avatar Orvid King Committed by Facebook Github Bot

Implement pthread_rwlock_t and pthread_cond_t in the portability layer

Summary:
The last bits of pthread needed for thrift.
Also define PTHREAD_MUTEX_NORMAL properly.

Reviewed By: yfeldblum

Differential Revision: D9846774

fbshipit-source-id: 124013c6df07f9faa214b77fa8ebe8b37d8f460e
parent fd99fa95
......@@ -22,9 +22,11 @@
#include <errno.h>
#include <chrono>
#include <condition_variable>
#include <exception>
#include <limits>
#include <mutex>
#include <shared_mutex>
#include <thread>
#include <folly/lang/Assume.h>
......@@ -278,7 +280,7 @@ struct pthread_mutex_t_ {
public:
pthread_mutex_t_(int mutex_type) : type(mutex_type) {
switch (type) {
case PTHREAD_MUTEX_DEFAULT:
case PTHREAD_MUTEX_NORMAL:
new (&timed_mtx) std::timed_mutex();
break;
case PTHREAD_MUTEX_RECURSIVE:
......@@ -289,7 +291,7 @@ struct pthread_mutex_t_ {
~pthread_mutex_t_() noexcept {
switch (type) {
case PTHREAD_MUTEX_DEFAULT:
case PTHREAD_MUTEX_NORMAL:
timed_mtx.~timed_mutex();
break;
case PTHREAD_MUTEX_RECURSIVE:
......@@ -300,7 +302,7 @@ struct pthread_mutex_t_ {
void lock() {
switch (type) {
case PTHREAD_MUTEX_DEFAULT:
case PTHREAD_MUTEX_NORMAL:
timed_mtx.lock();
break;
case PTHREAD_MUTEX_RECURSIVE:
......@@ -311,7 +313,7 @@ struct pthread_mutex_t_ {
bool try_lock() {
switch (type) {
case PTHREAD_MUTEX_DEFAULT:
case PTHREAD_MUTEX_NORMAL:
return timed_mtx.try_lock();
case PTHREAD_MUTEX_RECURSIVE:
return recursive_timed_mtx.try_lock();
......@@ -321,7 +323,7 @@ struct pthread_mutex_t_ {
bool timed_try_lock(std::chrono::system_clock::time_point until) {
switch (type) {
case PTHREAD_MUTEX_DEFAULT:
case PTHREAD_MUTEX_NORMAL:
return timed_mtx.try_lock_until(until);
case PTHREAD_MUTEX_RECURSIVE:
return recursive_timed_mtx.try_lock_until(until);
......@@ -331,7 +333,7 @@ struct pthread_mutex_t_ {
void unlock() {
switch (type) {
case PTHREAD_MUTEX_DEFAULT:
case PTHREAD_MUTEX_NORMAL:
timed_mtx.unlock();
break;
case PTHREAD_MUTEX_RECURSIVE:
......@@ -339,6 +341,37 @@ struct pthread_mutex_t_ {
break;
}
}
void condition_wait(std::condition_variable_any& cond) {
switch (type) {
case PTHREAD_MUTEX_NORMAL: {
std::unique_lock<std::timed_mutex> lock(timed_mtx);
cond.wait(lock);
break;
}
case PTHREAD_MUTEX_RECURSIVE: {
std::unique_lock<std::recursive_timed_mutex> lock(recursive_timed_mtx);
cond.wait(lock);
break;
}
}
}
bool condition_timed_wait(
std::condition_variable_any& cond,
std::chrono::system_clock::time_point until) {
switch (type) {
case PTHREAD_MUTEX_NORMAL: {
std::unique_lock<std::timed_mutex> lock(timed_mtx);
return cond.wait_until(lock, until) == std::cv_status::no_timeout;
}
case PTHREAD_MUTEX_RECURSIVE: {
std::unique_lock<std::recursive_timed_mutex> lock(recursive_timed_mtx);
return cond.wait_until(lock, until) == std::cv_status::no_timeout;
}
}
folly::assume_unreachable();
}
};
int pthread_mutex_init(
......@@ -387,6 +420,14 @@ int pthread_mutex_trylock(pthread_mutex_t* mutex) {
}
}
static std::chrono::system_clock::time_point timespec_to_time_point(
const timespec* t) {
using time_point = std::chrono::system_clock::time_point;
auto ns =
std::chrono::seconds(t->tv_sec) + std::chrono::nanoseconds(t->tv_nsec);
return time_point(std::chrono::duration_cast<time_point::duration>(ns));
}
int pthread_mutex_timedlock(
pthread_mutex_t* mutex,
const timespec* abs_timeout) {
......@@ -394,11 +435,7 @@ int pthread_mutex_timedlock(
return EINVAL;
}
using time_point = std::chrono::system_clock::time_point;
auto ns = std::chrono::seconds(abs_timeout->tv_sec) +
std::chrono::nanoseconds(abs_timeout->tv_nsec);
auto time = time_point(std::chrono::duration_cast<time_point::duration>(ns));
auto time = timespec_to_time_point(abs_timeout);
if ((*mutex)->timed_try_lock(time)) {
return 0;
} else {
......@@ -417,6 +454,202 @@ int pthread_mutex_unlock(pthread_mutex_t* mutex) {
return 0;
}
struct pthread_rwlock_t_ {
std::shared_timed_mutex mtx;
std::atomic<bool> writing{false};
};
int pthread_rwlock_init(pthread_rwlock_t* rwlock, const void* attr) {
if (attr != nullptr) {
return EINVAL;
}
if (rwlock == nullptr) {
return EINVAL;
}
*rwlock = new pthread_rwlock_t_();
return 0;
}
int pthread_rwlock_destroy(pthread_rwlock_t* rwlock) {
if (rwlock == nullptr) {
return EINVAL;
}
delete *rwlock;
*rwlock = nullptr;
return 0;
}
int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock) {
if (rwlock == nullptr) {
return EINVAL;
}
(*rwlock)->mtx.lock_shared();
return 0;
}
int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock) {
if (rwlock == nullptr) {
return EINVAL;
}
if ((*rwlock)->mtx.try_lock_shared()) {
return 0;
} else {
return EBUSY;
}
}
int pthread_rwlock_timedrdlock(
pthread_rwlock_t* rwlock,
const timespec* abs_timeout) {
if (rwlock == nullptr) {
return EINVAL;
}
auto time = timespec_to_time_point(abs_timeout);
if ((*rwlock)->mtx.try_lock_shared_until(time)) {
return 0;
} else {
return ETIMEDOUT;
}
}
int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock) {
if (rwlock == nullptr) {
return EINVAL;
}
(*rwlock)->mtx.lock();
(*rwlock)->writing = true;
return 0;
}
// Note: As far as I can tell, rwlock is technically supposed to
// be an upgradable lock, but we don't implement it that way.
int pthread_rwlock_trywrlock(pthread_rwlock_t* rwlock) {
if (rwlock == nullptr) {
return EINVAL;
}
if ((*rwlock)->mtx.try_lock()) {
(*rwlock)->writing = true;
return 0;
} else {
return EBUSY;
}
}
int pthread_rwlock_timedwrlock(
pthread_rwlock_t* rwlock,
const timespec* abs_timeout) {
if (rwlock == nullptr) {
return EINVAL;
}
auto time = timespec_to_time_point(abs_timeout);
if ((*rwlock)->mtx.try_lock_until(time)) {
(*rwlock)->writing = true;
return 0;
} else {
return ETIMEDOUT;
}
}
int pthread_rwlock_unlock(pthread_rwlock_t* rwlock) {
if (rwlock == nullptr) {
return EINVAL;
}
// Note: We don't have any checking to ensure we have actually
// locked things first, so you'll actually be in undefined behavior
// territory if you do attempt to unlock things you haven't locked.
if ((*rwlock)->writing) {
(*rwlock)->mtx.unlock();
// If we fail, then another thread has already immediately acquired
// the write lock, so this should stay as true :)
bool dump = true;
(void)(*rwlock)->writing.compare_exchange_strong(dump, false);
} else {
(*rwlock)->mtx.unlock_shared();
}
return 0;
}
struct pthread_cond_t_ {
// pthread_mutex_t is backed by timed
// mutexes, so no basic condition variable for
// us :(
std::condition_variable_any cond;
};
int pthread_cond_init(pthread_cond_t* cond, const void* attr) {
if (attr != nullptr) {
return EINVAL;
}
if (cond == nullptr) {
return EINVAL;
}
*cond = new pthread_cond_t_();
return 0;
}
int pthread_cond_destroy(pthread_cond_t* cond) {
if (cond == nullptr) {
return EINVAL;
}
delete *cond;
*cond = nullptr;
return 0;
}
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex) {
if (cond == nullptr || mutex == nullptr) {
return EINVAL;
}
(*mutex)->condition_wait((*cond)->cond);
return 0;
}
int pthread_cond_timedwait(
pthread_cond_t* cond,
pthread_mutex_t* mutex,
const timespec* abstime) {
if (cond == nullptr || mutex == nullptr || abstime == nullptr) {
return EINVAL;
}
auto time = timespec_to_time_point(abstime);
if ((*mutex)->condition_timed_wait((*cond)->cond, time)) {
return 0;
} else {
return ETIMEDOUT;
}
}
int pthread_cond_signal(pthread_cond_t* cond) {
if (cond == nullptr) {
return EINVAL;
}
(*cond)->cond.notify_one();
return 0;
}
int pthread_cond_broadcast(pthread_cond_t* cond) {
if (cond == nullptr) {
return EINVAL;
}
(*cond)->cond.notify_all();
return 0;
}
int pthread_key_create(pthread_key_t* key, void (*destructor)(void*)) {
try {
auto newKey = new boost::thread_specific_ptr<void>(destructor);
......
......@@ -34,8 +34,9 @@
#define PTHREAD_CREATE_JOINABLE 0
#define PTHREAD_CREATE_DETACHED 1
#define PTHREAD_MUTEX_DEFAULT 0
#define PTHREAD_MUTEX_NORMAL 0
#define PTHREAD_MUTEX_RECURSIVE 1
#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
#define _POSIX_TIMEOUTS 200112L
......@@ -96,6 +97,39 @@ int pthread_mutex_timedlock(
pthread_mutex_t* mutex,
const timespec* abs_timeout);
using pthread_rwlock_t = struct pthread_rwlock_t_*;
// Technically the second argument here is supposed to be a
// const pthread_rwlockattr_t* but we don support it, so we
// simply don't define pthread_rwlockattr_t at all to cause
// a build-break if anyone tries to use it.
int pthread_rwlock_init(pthread_rwlock_t* rwlock, const void* attr);
int pthread_rwlock_destroy(pthread_rwlock_t* rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock);
int pthread_rwlock_timedrdlock(
pthread_rwlock_t* rwlock,
const timespec* abs_timeout);
int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t* rwlock);
int pthread_rwlock_timedwrlock(
pthread_rwlock_t* rwlock,
const timespec* abs_timeout);
int pthread_rwlock_unlock(pthread_rwlock_t* rwlock);
using pthread_cond_t = struct pthread_cond_t_*;
// Once again, technically the second argument should be a
// pthread_condattr_t, but we don't implement it, so void*
// it is.
int pthread_cond_init(pthread_cond_t* cond, const void* attr);
int pthread_cond_destroy(pthread_cond_t* cond);
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
int pthread_cond_timedwait(
pthread_cond_t* cond,
pthread_mutex_t* mutex,
const timespec* abstime);
int pthread_cond_signal(pthread_cond_t* cond);
int pthread_cond_broadcast(pthread_cond_t* cond);
// In reality, this is boost::thread_specific_ptr*, but we're attempting
// to avoid introducing boost into a portability header.
using pthread_key_t = void*;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment