Merge pull request #36 from cynnyx/master

threads.hpp + tests
This commit is contained in:
Michele Caini 2016-08-01 12:31:11 +02:00 committed by GitHub
commit f9c5cb4df5
11 changed files with 534 additions and 39 deletions

View File

@ -116,7 +116,12 @@ The first thing to do to use `uvw` is to create a loop. In case the default one
auto loop = uvw::Loop::getDefault();
Note that loop objects don't require to be closed explicitly, even if they offer the `close` member method in case an user wants to do that.
Loops can be run using the `run`, `runOnce` and `runWait` member methods. Please refer to the documentation of *libuv* for further details.
Loops can be started using the `run` member method. The two calls below are equivalent:
loop->run();
loop->run<uvw::Loop::Mode::DEFAULT>
Available modes are: `DEFAULT`, `ONCE`, `NOWAIT`. Please refer to the documentation of *libuv* for further details.
In order to create a resource and to bind it to the given loop, just do the following:

View File

@ -22,6 +22,13 @@ enum class UVLoopOption: std::underlying_type_t<uv_loop_option> {
};
enum class UVRunMode: std::underlying_type_t<uv_run_mode> {
DEFAULT = UV_RUN_DEFAULT,
ONCE = UV_RUN_ONCE,
NOWAIT = UV_RUN_NOWAIT
};
}
@ -49,6 +56,7 @@ class Loop final: public Emitter<Loop>, public std::enable_shared_from_this<Loop
public:
using Time = std::chrono::milliseconds;
using Configure = details::UVLoopOption;
using Mode = details::UVRunMode;
static std::shared_ptr<Loop> create() {
auto ptr = std::unique_ptr<uv_loop_t, Deleter>{new uv_loop_t, [](uv_loop_t *l){ delete l; }};
@ -117,16 +125,11 @@ public:
if(err) { publish(ErrorEvent{err}); }
}
template<Mode mode = Mode::DEFAULT>
bool run() noexcept {
return (uv_run(loop.get(), UV_RUN_DEFAULT) == 0);
}
bool runOnce() noexcept {
return (uv_run(loop.get(), UV_RUN_ONCE) == 0);
}
bool runWait() noexcept {
return (uv_run(loop.get(), UV_RUN_NOWAIT) == 0);
auto utm = static_cast<std::underlying_type_t<Mode>>(mode);
auto uvrm = static_cast<uv_run_mode>(utm);
return (uv_run(loop.get(), uvrm) == 0);
}
bool alive() const noexcept {

View File

@ -1,6 +1,7 @@
#pragma once
#include <type_traits>
#include <utility>
#include <memory>
#include <string>
@ -11,6 +12,16 @@
namespace uvw {
class Thread;
class ThreadLocalStorage;
class Once;
class Mutex;
class RWLock;
class Semaphore;
class Condition;
class Barrier;
class Thread final {
using InternalTask = std::function<void(std::shared_ptr<void>)>;
@ -23,8 +34,7 @@ class Thread final {
: pLoop{std::move(ref)},
data{std::move(d)},
thread{},
task{std::move(t)},
err{0}
task{std::move(t)}
{ }
public:
@ -44,24 +54,18 @@ public:
return !(0 == uv_thread_equal(&tl.thread, &tr.thread));
}
~Thread() {
~Thread() noexcept {
uv_thread_join(&thread);
}
bool run() noexcept {
err = uv_thread_create(&thread, &createCallback, this);
return static_cast<bool>(*this);
return (0 == uv_thread_create(&thread, &createCallback, this));
}
bool join() noexcept {
err = uv_thread_join(&thread);
return static_cast<bool>(*this);
return (0 == uv_thread_join(&thread));
}
explicit operator bool() const noexcept { return (0 == err); }
int error() const noexcept { return err; }
Loop& loop() const noexcept { return *pLoop; }
private:
@ -69,17 +73,266 @@ private:
std::shared_ptr<void> data;
uv_thread_t thread;
Task task;
int err;
};
// TODO Thread-local storage
// TODO Once-only initialization
// TODO Mutex locks
// TODO Read-write locks
// TODO Semaphores
// TODO Conditions
// TODO Barriers
class ThreadLocalStorage final {
explicit ThreadLocalStorage(std::shared_ptr<Loop> ref) noexcept
: pLoop{std::move(ref)}
{
uv_key_create(&key);
}
public:
template<typename... Args>
static std::shared_ptr<ThreadLocalStorage> create(Args&&... args) {
return std::shared_ptr<ThreadLocalStorage>{new ThreadLocalStorage{std::forward<Args>(args)...}};
}
~ThreadLocalStorage() noexcept {
uv_key_delete(&key);
}
template<typename T>
T* get() noexcept {
return static_cast<T*>(uv_key_get(&key));
}
template<typename T>
void set(T *value) noexcept {
return uv_key_set(&key, value);
}
Loop& loop() const noexcept { return *pLoop; }
private:
std::shared_ptr<Loop> pLoop;
uv_key_t key;
};
class Once final {
explicit Once(std::shared_ptr<Loop> ref) noexcept
: pLoop{std::move(ref)}
{ }
public:
template<typename... Args>
static std::shared_ptr<Once> create(Args&&... args) {
return std::shared_ptr<Once>{new Once{std::forward<Args>(args)...}};
}
template<typename F>
static void once(F &&f) noexcept {
using CallbackType = void (*)(void);
static_assert(std::is_convertible<F, CallbackType>::value, "!");
CallbackType cb = f;
uv_once(&guard, cb);
}
Loop& loop() const noexcept { return *pLoop; }
private:
std::shared_ptr<Loop> pLoop;
static uv_once_t guard;
};
uv_once_t Once::guard = UV_ONCE_INIT;
class Mutex final {
friend class Condition;
explicit Mutex(std::shared_ptr<Loop> ref) noexcept
: pLoop{std::move(ref)}
{
uv_mutex_init(&mutex);
}
public:
template<typename... Args>
static std::shared_ptr<Mutex> create(Args&&... args) {
return std::shared_ptr<Mutex>{new Mutex{std::forward<Args>(args)...}};
}
~Mutex() noexcept {
uv_mutex_destroy(&mutex);
}
void lock() noexcept {
uv_mutex_lock(&mutex);
}
bool tryLock() noexcept {
return (0 == uv_mutex_trylock(&mutex));
}
void unlock() noexcept {
uv_mutex_unlock(&mutex);
}
Loop& loop() const noexcept { return *pLoop; }
private:
std::shared_ptr<Loop> pLoop;
uv_mutex_t mutex;
};
class RWLock final {
explicit RWLock(std::shared_ptr<Loop> ref) noexcept
: pLoop{std::move(ref)}
{
uv_rwlock_init(&rwlock);
}
public:
template<typename... Args>
static std::shared_ptr<RWLock> create(Args&&... args) {
return std::shared_ptr<RWLock>{new RWLock{std::forward<Args>(args)...}};
}
~RWLock() noexcept {
uv_rwlock_destroy(&rwlock);
}
void rdLock() noexcept {
uv_rwlock_rdlock(&rwlock);
}
bool tryRdLock() noexcept {
return (0 == uv_rwlock_tryrdlock(&rwlock));
}
void rdUnlock() noexcept {
uv_rwlock_rdunlock(&rwlock);
}
void wrLock() noexcept {
uv_rwlock_wrlock(&rwlock);
}
bool tryWrLock() noexcept {
return (0 == uv_rwlock_trywrlock(&rwlock));
}
void wrUnlock() noexcept {
uv_rwlock_wrunlock(&rwlock);
}
Loop& loop() const noexcept { return *pLoop; }
private:
std::shared_ptr<Loop> pLoop;
uv_rwlock_t rwlock;
};
class Semaphore final {
explicit Semaphore(std::shared_ptr<Loop> ref, unsigned int value) noexcept
: pLoop{std::move(ref)}
{
uv_sem_init(&sem, value);
}
public:
template<typename... Args>
static std::shared_ptr<Semaphore> create(Args&&... args) {
return std::shared_ptr<Semaphore>{new Semaphore{std::forward<Args>(args)...}};
}
~Semaphore() noexcept {
uv_sem_destroy(&sem);
}
void post() noexcept {
uv_sem_post(&sem);
}
void wait() noexcept {
uv_sem_wait(&sem);
}
bool tryWait() noexcept {
return (0 == uv_sem_trywait(&sem));
}
Loop& loop() const noexcept { return *pLoop; }
private:
std::shared_ptr<Loop> pLoop;
uv_sem_t sem;
};
class Condition final {
explicit Condition(std::shared_ptr<Loop> ref) noexcept
: pLoop{std::move(ref)}
{
uv_cond_init(&cond);
}
public:
template<typename... Args>
static std::shared_ptr<Condition> create(Args&&... args) {
return std::shared_ptr<Condition>{new Condition{std::forward<Args>(args)...}};
}
~Condition() noexcept {
uv_cond_destroy(&cond);
}
void signal() noexcept {
uv_cond_signal(&cond);
}
void broadcast() noexcept {
uv_cond_broadcast(&cond);
}
void wait(Mutex &mutex) noexcept {
uv_cond_wait(&cond, &mutex.mutex);
}
bool timedWait(Mutex &mutex, uint64_t timeout) noexcept {
return (0 == uv_cond_timedwait(&cond, &mutex.mutex, timeout));
}
Loop& loop() const noexcept { return *pLoop; }
private:
std::shared_ptr<Loop> pLoop;
uv_cond_t cond;
};
class Barrier final {
explicit Barrier(std::shared_ptr<Loop> ref, unsigned int count) noexcept
: pLoop{std::move(ref)}
{
uv_barrier_init(&barrier, count);
}
public:
template<typename... Args>
static std::shared_ptr<Barrier> create(Args&&... args) {
return std::shared_ptr<Barrier>{new Barrier{std::forward<Args>(args)...}};
}
~Barrier() noexcept {
uv_barrier_destroy(&barrier);
}
bool wait() noexcept {
return (0 == uv_barrier_wait(&barrier));
}
Loop& loop() const noexcept { return *pLoop; }
private:
std::shared_ptr<Loop> pLoop;
uv_barrier_t barrier;
};
}

View File

@ -22,7 +22,11 @@ set(
set(TARGET_MAIN main)
set(TARGET_ASYNC async)
set(TARGET_CHECK check)
set(TARGET_IDLE idle)
set(TARGET_LOOP loop)
set(TARGET_PREPARE prepare)
set(TARGET_SELF self)
set(TARGET_WORK work)
# Test TARGET_MAIN
@ -41,6 +45,22 @@ target_include_directories(${TARGET_ASYNC} PRIVATE ${COMMON_INCLUDE_DIRS})
target_link_libraries(${TARGET_ASYNC} PRIVATE ${COMMON_LINK_LIBS})
add_test(NAME ${TARGET_ASYNC} COMMAND ${TARGET_ASYNC})
# Test TARGET_CHECK
set(TARGET_CHECK_SOURCES uvw/check.cpp)
add_executable(${TARGET_CHECK} ${TARGET_CHECK_SOURCES})
target_include_directories(${TARGET_CHECK} PRIVATE ${COMMON_INCLUDE_DIRS})
target_link_libraries(${TARGET_CHECK} PRIVATE ${COMMON_LINK_LIBS})
add_test(NAME ${TARGET_CHECK} COMMAND ${TARGET_CHECK})
# Test TARGET_IDLE
set(TARGET_IDLE_SOURCES uvw/idle.cpp)
add_executable(${TARGET_IDLE} ${TARGET_IDLE_SOURCES})
target_include_directories(${TARGET_IDLE} PRIVATE ${COMMON_INCLUDE_DIRS})
target_link_libraries(${TARGET_IDLE} PRIVATE ${COMMON_LINK_LIBS})
add_test(NAME ${TARGET_IDLE} COMMAND ${TARGET_IDLE})
# Test TARGET_LOOP
set(TARGET_LOOP_SOURCES uvw/loop.cpp)
@ -49,6 +69,22 @@ target_include_directories(${TARGET_LOOP} PRIVATE ${COMMON_INCLUDE_DIRS})
target_link_libraries(${TARGET_LOOP} PRIVATE ${COMMON_LINK_LIBS})
add_test(NAME ${TARGET_LOOP} COMMAND ${TARGET_LOOP})
# Test TARGET_PREPARE
set(TARGET_PREPARE_SOURCES uvw/prepare.cpp)
add_executable(${TARGET_PREPARE} ${TARGET_PREPARE_SOURCES})
target_include_directories(${TARGET_PREPARE} PRIVATE ${COMMON_INCLUDE_DIRS})
target_link_libraries(${TARGET_PREPARE} PRIVATE ${COMMON_LINK_LIBS})
add_test(NAME ${TARGET_PREPARE} COMMAND ${TARGET_PREPARE})
# Test TARGET_SELF
set(TARGET_SELF_SOURCES uvw/self.cpp)
add_executable(${TARGET_SELF} ${TARGET_SELF_SOURCES})
target_include_directories(${TARGET_SELF} PRIVATE ${COMMON_INCLUDE_DIRS})
target_link_libraries(${TARGET_SELF} PRIVATE ${COMMON_LINK_LIBS})
add_test(NAME ${TARGET_SELF} COMMAND ${TARGET_SELF})
# Test TARGET_WORK
set(TARGET_WORK_SOURCES uvw/work.cpp)

View File

@ -1,6 +1,7 @@
#include <gtest/gtest.h>
#include <uvw.hpp>
TEST(Async, Send) {
auto loop = uvw::Loop::getDefault();
auto handle = loop->resource<uvw::AsyncHandle>();
@ -8,12 +9,12 @@ TEST(Async, Send) {
bool checkErrorEvent = false;
bool checkAsyncEvent = false;
handle->on<uvw::ErrorEvent>([&checkErrorEvent](const uvw::ErrorEvent &, uvw::AsyncHandle &){
handle->on<uvw::ErrorEvent>([&checkErrorEvent](const auto &, auto &){
ASSERT_FALSE(checkErrorEvent);
checkErrorEvent = true;
});
handle->on<uvw::AsyncEvent>([&checkAsyncEvent](const uvw::AsyncEvent &, uvw::AsyncHandle &handle){
handle->on<uvw::AsyncEvent>([&checkAsyncEvent](const auto &, auto &handle){
ASSERT_FALSE(checkAsyncEvent);
checkAsyncEvent = true;
handle.close();
@ -30,3 +31,21 @@ TEST(Async, Send) {
ASSERT_FALSE(checkErrorEvent);
ASSERT_TRUE(checkAsyncEvent);
}
TEST(Async, Fake) {
auto loop = uvw::Loop::getDefault();
auto handle = loop->resource<uvw::AsyncHandle>();
auto l = [](const auto &, auto &){ ASSERT_FALSE(true); };
handle->on<uvw::ErrorEvent>(l);
handle->on<uvw::AsyncEvent>(l);
handle->send();
handle->close();
ASSERT_FALSE(handle->active());
ASSERT_TRUE(handle->closing());
loop->run();
}

52
test/uvw/check.cpp Normal file
View File

@ -0,0 +1,52 @@
#include <gtest/gtest.h>
#include <uvw.hpp>
TEST(Check, StartAndStop) {
auto loop = uvw::Loop::getDefault();
auto handle = loop->resource<uvw::CheckHandle>();
bool checkErrorEvent = false;
bool checkCheckEvent = false;
handle->on<uvw::ErrorEvent>([&checkErrorEvent](const auto &, auto &){
ASSERT_FALSE(checkErrorEvent);
checkErrorEvent = true;
});
handle->on<uvw::CheckEvent>([&checkCheckEvent](const auto &, auto &handle){
ASSERT_FALSE(checkCheckEvent);
checkCheckEvent = true;
handle.stop();
handle.close();
ASSERT_TRUE(handle.closing());
});
handle->start();
ASSERT_TRUE(handle->active());
ASSERT_FALSE(handle->closing());
loop->run<uvw::Loop::Mode::NOWAIT>();
ASSERT_FALSE(checkErrorEvent);
ASSERT_TRUE(checkCheckEvent);
}
TEST(Check, Fake) {
auto loop = uvw::Loop::getDefault();
auto handle = loop->resource<uvw::CheckHandle>();
auto l = [](const auto &, auto &){ ASSERT_FALSE(true); };
handle->on<uvw::ErrorEvent>(l);
handle->on<uvw::CheckEvent>(l);
handle->start();
handle->close();
ASSERT_FALSE(handle->active());
ASSERT_TRUE(handle->closing());
loop->run();
}

52
test/uvw/idle.cpp Normal file
View File

@ -0,0 +1,52 @@
#include <gtest/gtest.h>
#include <uvw.hpp>
TEST(Idle, StartAndStop) {
auto loop = uvw::Loop::getDefault();
auto handle = loop->resource<uvw::IdleHandle>();
bool checkErrorEvent = false;
bool checkIdleEvent = false;
handle->on<uvw::ErrorEvent>([&checkErrorEvent](const auto &, auto &){
ASSERT_FALSE(checkErrorEvent);
checkErrorEvent = true;
});
handle->on<uvw::IdleEvent>([&checkIdleEvent](const auto &, auto &handle){
ASSERT_FALSE(checkIdleEvent);
checkIdleEvent = true;
handle.stop();
handle.close();
ASSERT_TRUE(handle.closing());
});
handle->start();
ASSERT_TRUE(handle->active());
ASSERT_FALSE(handle->closing());
loop->run<uvw::Loop::Mode::NOWAIT>();
ASSERT_FALSE(checkErrorEvent);
ASSERT_TRUE(checkIdleEvent);
}
TEST(Idle, Fake) {
auto loop = uvw::Loop::getDefault();
auto handle = loop->resource<uvw::IdleHandle>();
auto l = [](const auto &, auto &){ ASSERT_FALSE(true); };
handle->on<uvw::ErrorEvent>(l);
handle->on<uvw::IdleEvent>(l);
handle->start();
handle->close();
ASSERT_FALSE(handle->active());
ASSERT_TRUE(handle->closing());
loop->run();
}

View File

@ -15,7 +15,7 @@ TEST(Loop, PartiallyDone) {
auto handle = loop->resource<uvw::PrepareHandle>();
auto req = loop->resource<uvw::WorkReq>([](){});
auto err = [](uvw::ErrorEvent, auto &) { ASSERT_TRUE(false); };
auto err = [](const auto &, auto &) { ASSERT_TRUE(false); };
loop->on<uvw::ErrorEvent>(err);
req->on<uvw::ErrorEvent>(err);
@ -27,7 +27,7 @@ TEST(Loop, PartiallyDone) {
ASSERT_FALSE(loop->alive());
handle->start();
handle->on<uvw::PrepareEvent>([](uvw::PrepareEvent, uvw::PrepareHandle &handle) {
handle->on<uvw::PrepareEvent>([](const auto &, auto &handle) {
handle.loop().walk([](uvw::BaseHandle &) {
static bool trigger = true;
ASSERT_TRUE(trigger);
@ -42,8 +42,8 @@ TEST(Loop, PartiallyDone) {
loop->walk([](uvw::BaseHandle &) { ASSERT_TRUE(false); });
ASSERT_NO_THROW(loop->runOnce());
ASSERT_NO_THROW(loop->runWait());
ASSERT_NO_THROW(loop->run<uvw::Loop::Mode::ONCE>());
ASSERT_NO_THROW(loop->run<uvw::Loop::Mode::NOWAIT>());
ASSERT_FALSE(loop->alive());
}

52
test/uvw/prepare.cpp Normal file
View File

@ -0,0 +1,52 @@
#include <gtest/gtest.h>
#include <uvw.hpp>
TEST(Prepare, StartAndStop) {
auto loop = uvw::Loop::getDefault();
auto handle = loop->resource<uvw::PrepareHandle>();
bool checkErrorEvent = false;
bool checkPrepareEvent = false;
handle->on<uvw::ErrorEvent>([&checkErrorEvent](const auto &, auto &){
ASSERT_FALSE(checkErrorEvent);
checkErrorEvent = true;
});
handle->on<uvw::PrepareEvent>([&checkPrepareEvent](const auto &, auto &handle){
ASSERT_FALSE(checkPrepareEvent);
checkPrepareEvent = true;
handle.stop();
handle.close();
ASSERT_TRUE(handle.closing());
});
handle->start();
ASSERT_TRUE(handle->active());
ASSERT_FALSE(handle->closing());
loop->run<uvw::Loop::Mode::NOWAIT>();
ASSERT_FALSE(checkErrorEvent);
ASSERT_TRUE(checkPrepareEvent);
}
TEST(Prepare, Fake) {
auto loop = uvw::Loop::getDefault();
auto handle = loop->resource<uvw::PrepareHandle>();
auto l = [](const auto &, auto &){ ASSERT_FALSE(true); };
handle->on<uvw::ErrorEvent>(l);
handle->on<uvw::PrepareEvent>(l);
handle->start();
handle->close();
ASSERT_FALSE(handle->active());
ASSERT_TRUE(handle->closing());
loop->run();
}

23
test/uvw/self.cpp Normal file
View File

@ -0,0 +1,23 @@
#include <gtest/gtest.h>
#include <uvw.hpp>
struct S: uvw::Self<S> { };
TEST(Self, Basics) {
std::shared_ptr<S> self = std::make_shared<S>();
ASSERT_TRUE(self.unique());
ASSERT_FALSE(self->self());
self->leak();
ASSERT_FALSE(self.unique());
ASSERT_TRUE(self->self());
self->reset();
ASSERT_TRUE(self.unique());
ASSERT_FALSE(self->self());
}

View File

@ -14,12 +14,12 @@ TEST(Work, RunTask) {
checkTask = true;
});
req->on<uvw::WorkEvent>([&checkWorkEvent](const uvw::WorkEvent &, uvw::WorkReq &){
req->on<uvw::WorkEvent>([&checkWorkEvent](const auto &, auto &){
ASSERT_FALSE(checkWorkEvent);
checkWorkEvent = true;
});
req->on<uvw::ErrorEvent>([&checkErrorEvent](const uvw::ErrorEvent &, uvw::WorkReq &){
req->on<uvw::ErrorEvent>([&checkErrorEvent](const auto &, auto &){
ASSERT_FALSE(checkErrorEvent);
checkErrorEvent = true;
});
@ -44,12 +44,12 @@ TEST(Work, Cancellation) {
checkTask = true;
});
req->on<uvw::WorkEvent>([&checkWorkEvent](const uvw::WorkEvent &, uvw::WorkReq &){
req->on<uvw::WorkEvent>([&checkWorkEvent](const auto &, auto &){
ASSERT_FALSE(checkWorkEvent);
checkWorkEvent = true;
});
req->on<uvw::ErrorEvent>([&checkErrorEvent](const uvw::ErrorEvent &, uvw::WorkReq &){
req->on<uvw::ErrorEvent>([&checkErrorEvent](const auto &, auto &){
ASSERT_FALSE(checkErrorEvent);
checkErrorEvent = true;
});