Merge branch 'develop' into master

This commit is contained in:
木头云 2021-08-29 11:05:24 +08:00 committed by GitHub
commit 78be14be37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 811 additions and 154 deletions

View File

@ -1,31 +0,0 @@
language: cpp
os: linux
dist: xenial
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-8
matrix:
include:
- compiler: gcc
env:
- MATRIX_EVAL="CC=gcc-8 && CXX=g++-8"
- compiler: clang
before_install:
- eval "${MATRIX_EVAL}"
script:
- mkdir -p ./build && cd ./build
- cmake -DCMAKE_BUILD_TYPE=Release -DLIBIPC_BUILD_TESTS=ON ..
- make -j`nproc`
- export LD_LIBRARY_PATH=./lib:$LD_LIBRARY_PATH && ./bin/test-ipc
notifications:
slack:
on_success: never
on_failure: never

View File

@ -25,13 +25,16 @@ using uint_t = typename uint<N>::type;
// constants
enum : std::uint32_t {
invalid_value = (std::numeric_limits<std::uint32_t>::max)(),
default_timeout = 100, // ms
};
enum : std::size_t {
invalid_value = (std::numeric_limits<std::size_t>::max)(),
data_length = 64,
large_msg_limit = data_length,
large_msg_align = 1024,
large_msg_cache = 32,
default_timeout = 100 // ms
};
enum class relat { // multiplicity of the relationship

View File

@ -27,12 +27,12 @@ struct IPC_EXPORT chan_impl {
static char const * name(ipc::handle_t h);
static std::size_t recv_count(ipc::handle_t h);
static bool wait_for_recv(ipc::handle_t h, std::size_t r_count, std::size_t tm);
static bool wait_for_recv(ipc::handle_t h, std::size_t r_count, std::uint64_t tm);
static bool send(ipc::handle_t h, void const * data, std::size_t size, std::size_t tm);
static buff_t recv(ipc::handle_t h, std::size_t tm);
static bool send(ipc::handle_t h, void const * data, std::size_t size, std::uint64_t tm);
static buff_t recv(ipc::handle_t h, std::uint64_t tm);
static bool try_send(ipc::handle_t h, void const * data, std::size_t size, std::size_t tm);
static bool try_send(ipc::handle_t h, void const * data, std::size_t size, std::uint64_t tm);
static buff_t try_recv(ipc::handle_t h);
};
@ -120,41 +120,41 @@ public:
return detail_t::recv_count(h_);
}
bool wait_for_recv(std::size_t r_count, std::size_t tm = invalid_value) const {
bool wait_for_recv(std::size_t r_count, std::uint64_t tm = invalid_value) const {
return detail_t::wait_for_recv(h_, r_count, tm);
}
static bool wait_for_recv(char const * name, std::size_t r_count, std::size_t tm = invalid_value) {
static bool wait_for_recv(char const * name, std::size_t r_count, std::uint64_t tm = invalid_value) {
return chan_wrapper(name).wait_for_recv(r_count, tm);
}
/**
* If timeout, this function would call 'force_push' to send the data forcibly.
*/
bool send(void const * data, std::size_t size, std::size_t tm = default_timeout) {
bool send(void const * data, std::size_t size, std::uint64_t tm = default_timeout) {
return detail_t::send(h_, data, size, tm);
}
bool send(buff_t const & buff, std::size_t tm = default_timeout) {
bool send(buff_t const & buff, std::uint64_t tm = default_timeout) {
return this->send(buff.data(), buff.size(), tm);
}
bool send(std::string const & str, std::size_t tm = default_timeout) {
bool send(std::string const & str, std::uint64_t tm = default_timeout) {
return this->send(str.c_str(), str.size() + 1, tm);
}
/**
* If timeout, this function would just return false.
*/
bool try_send(void const * data, std::size_t size, std::size_t tm = default_timeout) {
bool try_send(void const * data, std::size_t size, std::uint64_t tm = default_timeout) {
return detail_t::try_send(h_, data, size, tm);
}
bool try_send(buff_t const & buff, std::size_t tm = default_timeout) {
bool try_send(buff_t const & buff, std::uint64_t tm = default_timeout) {
return this->try_send(buff.data(), buff.size(), tm);
}
bool try_send(std::string const & str, std::size_t tm = default_timeout) {
bool try_send(std::string const & str, std::uint64_t tm = default_timeout) {
return this->try_send(str.c_str(), str.size() + 1, tm);
}
buff_t recv(std::size_t tm = invalid_value) {
buff_t recv(std::uint64_t tm = invalid_value) {
return detail_t::recv(h_, tm);
}

38
include/libipc/mutex.h Normal file
View File

@ -0,0 +1,38 @@
#pragma once
#include <cstdint> // std::uint64_t
#include <system_error>
#include "libipc/export.h"
#include "libipc/def.h"
namespace ipc {
namespace sync {
class IPC_EXPORT mutex {
mutex(mutex const &) = delete;
mutex &operator=(mutex const &) = delete;
public:
mutex();
explicit mutex(char const *name);
~mutex();
void const *native() const noexcept;
void *native() noexcept;
bool valid() const noexcept;
bool open(char const *name) noexcept;
void close() noexcept;
bool lock(std::uint64_t tm = ipc::invalid_value) noexcept;
bool try_lock() noexcept(false); // std::system_error
bool unlock() noexcept;
private:
class mutex_;
mutex_* p_;
};
} // namespace sync
} // namespace ipc

View File

@ -1,6 +1,7 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include "libipc/export.h"
@ -20,6 +21,9 @@ IPC_EXPORT void release(id_t id);
IPC_EXPORT void remove (id_t id);
IPC_EXPORT void remove (char const * name);
IPC_EXPORT std::uint32_t get_ref(id_t id);
IPC_EXPORT void sub_ref(id_t id);
class IPC_EXPORT handle {
public:
handle();
@ -31,9 +35,12 @@ public:
void swap(handle& rhs);
handle& operator=(handle rhs);
bool valid() const;
std::size_t size () const;
char const * name () const;
bool valid() const noexcept;
std::size_t size () const noexcept;
char const * name () const noexcept;
std::uint32_t ref() const noexcept;
void sub_ref() noexcept;
bool acquire(char const * name, std::size_t size, unsigned mode = create | open);
void release();

View File

@ -54,7 +54,7 @@ public:
bool open (char const * name, long count = 0);
void close();
bool wait(std::size_t tm = invalid_value);
bool wait(std::uint64_t tm = invalid_value);
bool post(long count = 1);
private:
@ -81,7 +81,7 @@ public:
bool open (char const * name);
void close();
bool wait(mutex&, std::size_t tm = invalid_value);
bool wait(mutex&, std::uint64_t tm = invalid_value);
bool notify();
bool broadcast();

View File

@ -300,7 +300,7 @@ struct conn_info_head {
};
template <typename W, typename F>
bool wait_for(W& waiter, F&& pred, std::size_t tm) {
bool wait_for(W& waiter, F&& pred, std::uint64_t tm) {
if (tm == 0) return !pred();
for (unsigned k = 0; pred();) {
bool loop = true, ret = true;
@ -414,7 +414,7 @@ static std::size_t recv_count(ipc::handle_t h) noexcept {
return que->conn_count();
}
static bool wait_for_recv(ipc::handle_t h, std::size_t r_count, std::size_t tm) {
static bool wait_for_recv(ipc::handle_t h, std::size_t r_count, std::uint64_t tm) {
auto que = queue_of(h);
if (que == nullptr) {
return false;
@ -487,7 +487,7 @@ static bool send(F&& gen_push, ipc::handle_t h, void const * data, std::size_t s
return true;
}
static bool send(ipc::handle_t h, void const * data, std::size_t size, std::size_t tm) {
static bool send(ipc::handle_t h, void const * data, std::size_t size, std::uint64_t tm) {
return send([tm](auto info, auto que, auto msg_id) {
return [tm, info, que, msg_id](std::int32_t remain, void const * data, std::size_t size) {
if (!wait_for(info->wt_waiter_, [&] {
@ -508,7 +508,7 @@ static bool send(ipc::handle_t h, void const * data, std::size_t size, std::size
}, h, data, size);
}
static bool try_send(ipc::handle_t h, void const * data, std::size_t size, std::size_t tm) {
static bool try_send(ipc::handle_t h, void const * data, std::size_t size, std::uint64_t tm) {
return send([tm](auto info, auto que, auto msg_id) {
return [tm, info, que, msg_id](std::int32_t remain, void const * data, std::size_t size) {
if (!wait_for(info->wt_waiter_, [&] {
@ -524,7 +524,7 @@ static bool try_send(ipc::handle_t h, void const * data, std::size_t size, std::
}, h, data, size);
}
static ipc::buff_t recv(ipc::handle_t h, std::size_t tm) {
static ipc::buff_t recv(ipc::handle_t h, std::uint64_t tm) {
auto que = queue_of(h);
if (que == nullptr) {
ipc::error("fail: recv, queue_of(h) == nullptr\n");
@ -666,22 +666,22 @@ std::size_t chan_impl<Flag>::recv_count(ipc::handle_t h) {
}
template <typename Flag>
bool chan_impl<Flag>::wait_for_recv(ipc::handle_t h, std::size_t r_count, std::size_t tm) {
bool chan_impl<Flag>::wait_for_recv(ipc::handle_t h, std::size_t r_count, std::uint64_t tm) {
return detail_impl<policy_t<Flag>>::wait_for_recv(h, r_count, tm);
}
template <typename Flag>
bool chan_impl<Flag>::send(ipc::handle_t h, void const * data, std::size_t size, std::size_t tm) {
bool chan_impl<Flag>::send(ipc::handle_t h, void const * data, std::size_t size, std::uint64_t tm) {
return detail_impl<policy_t<Flag>>::send(h, data, size, tm);
}
template <typename Flag>
buff_t chan_impl<Flag>::recv(ipc::handle_t h, std::size_t tm) {
buff_t chan_impl<Flag>::recv(ipc::handle_t h, std::uint64_t tm) {
return detail_impl<policy_t<Flag>>::recv(h, tm);
}
template <typename Flag>
bool chan_impl<Flag>::try_send(ipc::handle_t h, void const * data, std::size_t size, std::size_t tm) {
bool chan_impl<Flag>::try_send(ipc::handle_t h, void const * data, std::size_t size, std::uint64_t tm) {
return detail_impl<policy_t<Flag>>::try_send(h, data, size, tm);
}

View File

@ -22,6 +22,18 @@
# error "IPC_CONSTEXPR_ has been defined."
#endif
// detect platform
#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || \
defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || \
defined(WINCE) || defined(_WIN32_WCE)
# define IPC_OS_WINDOWS_
#endif/*WIN*/
#if defined(__linux__) || defined(__linux)
# define IPC_OS_LINUX_
#endif/*linux*/
#if __cplusplus >= 201703L
#define IPC_UNUSED_ [[maybe_unused]]

View File

@ -0,0 +1,39 @@
#pragma once
#include <cstdint>
#include <system_error>
#include <sys/time.h>
#include <time.h>
#include <errno.h>
#include "libipc/utility/log.h"
namespace ipc {
namespace detail {
inline bool calc_wait_time(timespec &ts, std::uint64_t tm /*ms*/) noexcept {
timeval now;
int eno = ::gettimeofday(&now, NULL);
if (eno != 0) {
ipc::error("fail gettimeofday [%d]\n", eno);
return false;
}
ts.tv_nsec = (now.tv_usec + (tm % 1000) * 1000) * 1000;
ts.tv_sec = now.tv_sec + (tm / 1000) + (ts.tv_nsec / 1000000000l);
ts.tv_nsec %= 1000000000l;
return true;
}
inline timespec make_timespec(std::uint64_t tm /*ms*/) noexcept(false) {
timespec ts {};
if (!calc_wait_time(ts, tm)) {
ipc::error("fail calc_wait_time: tm = %zd, tv_sec = %ld, tv_nsec = %ld\n",
tm, ts.tv_sec, ts.tv_nsec);
throw std::system_error{static_cast<int>(errno), std::system_category()};
}
return ts;
}
} // namespace detail
} // namespace ipc

View File

@ -0,0 +1,171 @@
#pragma once
#include <cstring>
#include <cassert>
#include <system_error>
#include <pthread.h>
#include "libipc/platform/get_wait_time.h"
#include "libipc/platform/detail.h"
#include "libipc/utility/log.h"
#include "libipc/utility/scope_guard.h"
#include "libipc/shm.h"
namespace ipc {
namespace detail {
namespace sync {
class mutex {
ipc::shm::handle shm_;
pthread_mutex_t *mutex_ = nullptr;
public:
mutex() = default;
explicit mutex(char const *name) noexcept {
open(name);
}
~mutex() = default;
pthread_mutex_t const *native() const noexcept {
return mutex_;
}
pthread_mutex_t *native() noexcept {
return mutex_;
}
bool valid() const noexcept {
static const char tmp[sizeof(pthread_mutex_t)] {};
return shm_.valid()
&& (mutex_ != nullptr)
&& (std::memcmp(tmp, mutex_, sizeof(pthread_mutex_t)) != 0);
}
bool open(char const *name) noexcept {
close();
if (!shm_.acquire(name, sizeof(pthread_mutex_t))) {
ipc::error("fail shm.acquire: %s\n", name);
return false;
}
mutex_ = static_cast<pthread_mutex_t *>(shm_.get());
assert(mutex_ != nullptr);
if ((shm_.ref() == 1) && valid()/*it means mutex has been inited*/) {
::pthread_mutex_destroy(mutex_);
}
auto finally = ipc::guard([this] { close(); }); // close when failed
// init mutex
int eno;
pthread_mutexattr_t mutex_attr;
if ((eno = ::pthread_mutexattr_init(&mutex_attr)) != 0) {
ipc::error("fail pthread_mutexattr_init[%d]\n", eno);
return false;
}
IPC_UNUSED_ auto guard_mutex_attr = unique_ptr(&mutex_attr, ::pthread_mutexattr_destroy);
if ((eno = ::pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED)) != 0) {
ipc::error("fail pthread_mutexattr_setpshared[%d]\n", eno);
return false;
}
if ((eno = ::pthread_mutexattr_setrobust(&mutex_attr, PTHREAD_MUTEX_ROBUST)) != 0) {
ipc::error("fail pthread_mutexattr_setrobust[%d]\n", eno);
return false;
}
*mutex_ = PTHREAD_MUTEX_INITIALIZER;
if ((eno = ::pthread_mutex_init(mutex_, &mutex_attr)) != 0) {
ipc::error("fail pthread_mutex_init[%d]\n", eno);
return false;
}
finally.dismiss();
return valid();
}
void close() noexcept {
if (shm_.ref() == 1) {
int eno;
if ((eno = ::pthread_mutex_destroy(mutex_)) != 0) {
ipc::error("fail pthread_mutex_destroy[%d]\n", eno);
}
}
shm_.release();
mutex_ = nullptr;
}
bool lock(std::uint64_t tm) noexcept {
for (;;) {
auto ts = detail::make_timespec(tm);
int eno = (tm == invalid_value)
? ::pthread_mutex_lock(mutex_)
: ::pthread_mutex_timedlock(mutex_, &ts);
switch (eno) {
case 0:
return true;
case ETIMEDOUT:
return false;
case EOWNERDEAD: {
if (shm_.ref() > 1) {
shm_.sub_ref();
}
int eno2 = ::pthread_mutex_consistent(mutex_);
if (eno2 != 0) {
ipc::error("fail pthread_mutex_lock[%d], pthread_mutex_consistent[%d]\n", eno, eno2);
return false;
}
int eno3 = ::pthread_mutex_unlock(mutex_);
if (eno3 != 0) {
ipc::error("fail pthread_mutex_lock[%d], pthread_mutex_unlock[%d]\n", eno, eno3);
return false;
}
}
break; // loop again
default:
ipc::error("fail pthread_mutex_lock[%d]\n", eno);
return false;
}
}
}
bool try_lock() noexcept(false) {
auto ts = detail::make_timespec(0);
int eno = ::pthread_mutex_timedlock(mutex_, &ts);
switch (eno) {
case 0:
return true;
case ETIMEDOUT:
return false;
case EOWNERDEAD: {
if (shm_.ref() > 1) {
shm_.sub_ref();
}
int eno2 = ::pthread_mutex_consistent(mutex_);
if (eno2 != 0) {
ipc::error("fail pthread_mutex_timedlock[%d], pthread_mutex_consistent[%d]\n", eno, eno2);
break;
}
int eno3 = ::pthread_mutex_unlock(mutex_);
if (eno3 != 0) {
ipc::error("fail pthread_mutex_timedlock[%d], pthread_mutex_unlock[%d]\n", eno, eno3);
break;
}
}
break;
default:
ipc::error("fail pthread_mutex_timedlock[%d]\n", eno);
break;
}
throw std::system_error{eno, std::system_category()};
}
bool unlock() noexcept {
int eno;
if ((eno = ::pthread_mutex_unlock(mutex_)) != 0) {
ipc::error("fail pthread_mutex_unlock[%d]\n", eno);
return false;
}
return true;
}
};
} // namespace sync
} // namespace detail
} // namespace ipc

View File

@ -0,0 +1,100 @@
#pragma once
#include <cstdint>
#include <system_error>
#include <Windows.h>
#include "libipc/utility/log.h"
#include "libipc/platform/to_tchar.h"
#include "libipc/platform/get_sa.h"
namespace ipc {
namespace detail {
namespace sync {
class mutex {
HANDLE h_ = NULL;
public:
mutex() noexcept = default;
explicit mutex(char const *name) noexcept {
open(name);
}
~mutex() noexcept = default;
HANDLE native() const noexcept {
return h_;
}
bool valid() const noexcept {
return h_ != NULL;
}
bool open(char const *name) noexcept {
close();
h_ = ::CreateMutex(detail::get_sa(), FALSE, ipc::detail::to_tchar(name).c_str());
if (h_ == NULL) {
ipc::error("fail CreateMutex[%lu]: %s\n", ::GetLastError(), name);
return false;
}
return true;
}
void close() noexcept {
if (!valid()) return;
::CloseHandle(h_);
h_ = NULL;
}
bool lock(std::uint64_t tm) noexcept {
DWORD ret, ms = (tm == invalid_value) ? INFINITE : static_cast<DWORD>(tm);
for(;;) {
switch ((ret = ::WaitForSingleObject(h_, ms))) {
case WAIT_OBJECT_0:
return true;
case WAIT_TIMEOUT:
return false;
case WAIT_ABANDONED:
ipc::log("fail WaitForSingleObject[%lu]: WAIT_ABANDONED, try again.\n", ::GetLastError());
if (!unlock()) {
return false;
}
break; // loop again
default:
ipc::error("fail WaitForSingleObject[%lu]: 0x%08X\n", ::GetLastError(), ret);
return false;
}
}
}
bool try_lock() noexcept(false) {
DWORD ret = ::WaitForSingleObject(h_, 0);
switch (ret) {
case WAIT_OBJECT_0:
return true;
case WAIT_TIMEOUT:
return false;
case WAIT_ABANDONED:
unlock();
IPC_FALLTHROUGH_;
default:
ipc::error("fail WaitForSingleObject[%lu]: 0x%08X\n", ::GetLastError(), ret);
throw std::system_error{static_cast<int>(ret), std::system_category()};
}
}
bool unlock() noexcept {
if (!::ReleaseMutex(h_)) {
ipc::error("fail ReleaseMutex[%lu]\n", ::GetLastError());
return false;
}
return true;
}
};
} // namespace sync
} // namespace detail
} // namespace ipc

View File

@ -22,7 +22,7 @@
namespace {
struct info_t {
std::atomic_size_t acc_;
std::atomic<std::uint32_t> acc_;
};
struct id_info_t {
@ -81,6 +81,31 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
return ii;
}
std::uint32_t get_ref(id_t id) {
if (id == nullptr) {
ipc::error("fail get_ref: invalid id (null)\n");
return 0;
}
auto ii = static_cast<id_info_t*>(id);
if (ii->mem_ == nullptr || ii->size_ == 0) {
return 0;
}
return acc_of(ii->mem_, ii->size_).load(std::memory_order_acquire);
}
void sub_ref(id_t id) {
if (id == nullptr) {
ipc::error("fail sub_ref: invalid id (null)\n");
return;
}
auto ii = static_cast<id_info_t*>(id);
if (ii->mem_ == nullptr || ii->size_ == 0) {
ipc::error("fail sub_ref: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
return;
}
acc_of(ii->mem_, ii->size_).fetch_sub(1, std::memory_order_acq_rel);
}
void * get_mem(id_t id, std::size_t * size) {
if (id == nullptr) {
ipc::error("fail get_mem: invalid id (null)\n");
@ -137,7 +162,7 @@ void release(id_t id) {
if (ii->mem_ == nullptr || ii->size_ == 0) {
ipc::error("fail release: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
}
else if (acc_of(ii->mem_, ii->size_).fetch_sub(1, std::memory_order_acquire) == 1) {
else if (acc_of(ii->mem_, ii->size_).fetch_sub(1, std::memory_order_acq_rel) == 1) {
::munmap(ii->mem_, ii->size_);
if (!ii->name_.empty()) {
::shm_unlink(ii->name_.c_str());

View File

@ -58,6 +58,14 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
return ii;
}
std::uint32_t get_ref(id_t) {
return 0;
}
void sub_ref(id_t) {
// Do Nothing.
}
void * get_mem(id_t id, std::size_t * size) {
if (id == nullptr) {
ipc::error("fail get_mem: invalid id (null)\n");

View File

@ -22,7 +22,7 @@
namespace ipc {
namespace detail {
inline static bool calc_wait_time(timespec& ts, std::size_t tm /*ms*/) {
inline static bool calc_wait_time(timespec& ts, std::uint64_t tm /*ms*/) {
timeval now;
int eno = ::gettimeofday(&now, NULL);
if (eno != 0) {
@ -89,8 +89,7 @@ public:
return true;
case EOWNERDEAD:
if (::pthread_mutex_consistent(&mutex_) == 0) {
::pthread_mutex_unlock(&mutex_);
break;
return true;
}
IPC_FALLTHROUGH_;
case ENOTRECOVERABLE:
@ -138,7 +137,7 @@ public:
IPC_PTHREAD_FUNC_(pthread_cond_destroy, &cond_);
}
bool wait(mutex& mtx, std::size_t tm = invalid_value) {
bool wait(mutex& mtx, std::uint64_t tm = invalid_value) {
switch (tm) {
case 0:
return true;
@ -221,7 +220,7 @@ public:
return true;
}
static bool wait(handle_t h, std::size_t tm = invalid_value) {
static bool wait(handle_t h, std::uint64_t tm = invalid_value) {
if (h == invalid()) return false;
switch (tm) {
case 0:
@ -289,7 +288,7 @@ private:
return ipc::detail::unique_lock(me_->lock_);
}
bool sema_wait(std::size_t tm) {
bool sema_wait(std::uint64_t tm) {
return sem_helper::wait(std::get<1>(h_), tm);
}
@ -297,7 +296,7 @@ private:
return sem_helper::post(std::get<1>(h_), count);
}
bool handshake_wait(std::size_t tm) {
bool handshake_wait(std::uint64_t tm) {
return sem_helper::wait(std::get<2>(h_), tm);
}
@ -339,7 +338,7 @@ public:
}
template <typename F>
bool wait_if(handle_t const & h, wait_flags * flags, F&& pred, std::size_t tm = invalid_value) {
bool wait_if(handle_t const & h, wait_flags * flags, F&& pred, std::uint64_t tm = invalid_value) {
assert(flags != nullptr);
contrl ctrl { this, flags, h };
@ -400,7 +399,7 @@ public:
}
template <typename F>
bool wait_if(handle_t h, waiter_helper::wait_flags * flags, F && pred, std::size_t tm = invalid_value) {
bool wait_if(handle_t h, waiter_helper::wait_flags * flags, F && pred, std::uint64_t tm = invalid_value) {
if (h == invalid()) return false;
return helper_.wait_if(h, flags, std::forward<F>(pred), tm);
}

View File

@ -40,7 +40,7 @@ public:
::CloseHandle(h_);
}
bool wait(std::size_t tm = invalid_value) {
bool wait(std::uint64_t tm = invalid_value) {
DWORD ret, ms = (tm == invalid_value) ? INFINITE : static_cast<DWORD>(tm);
switch ((ret = ::WaitForSingleObject(h_, ms))) {
case WAIT_OBJECT_0:
@ -102,7 +102,7 @@ class condition {
return ipc::detail::unique_lock(me_->lock_);
}
bool sema_wait(std::size_t tm) {
bool sema_wait(std::uint64_t tm) {
return me_->sema_.wait(tm);
}
@ -110,7 +110,7 @@ class condition {
return me_->sema_.post(count);
}
bool handshake_wait(std::size_t tm) {
bool handshake_wait(std::uint64_t tm) {
return me_->handshake_.wait(tm);
}
@ -151,7 +151,7 @@ public:
}
template <typename Mutex, typename F>
bool wait_if(Mutex & mtx, wait_flags * flags, F && pred, std::size_t tm = invalid_value) {
bool wait_if(Mutex & mtx, wait_flags * flags, F && pred, std::uint64_t tm = invalid_value) {
assert(flags != nullptr);
contrl ctrl { this, flags };
return waiter_helper::wait_if(ctrl, mtx, std::forward<F>(pred), tm);
@ -201,7 +201,7 @@ public:
}
template <typename F>
bool wait_if(handle_t& h, waiter_helper::wait_flags * flags, F&& pred, std::size_t tm = invalid_value) {
bool wait_if(handle_t& h, waiter_helper::wait_flags * flags, F&& pred, std::uint64_t tm = invalid_value) {
if (h == invalid()) return false;
class non_mutex {

View File

@ -8,9 +8,7 @@
#include "libipc/memory/resource.h"
#include "libipc/platform/detail.h"
#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || \
defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || \
defined(WINCE) || defined(_WIN32_WCE)
#if defined(IPC_OS_WINDOWS_)
#include "libipc/platform/waiter_win.h"
@ -52,7 +50,7 @@ public:
cnt_h_.release();
}
bool wait(mutex_impl& mtx, std::size_t tm = invalid_value) {
bool wait(mutex_impl& mtx, std::uint64_t tm = invalid_value) {
return base_t::wait_if(mtx, &flags_, [] { return true; }, tm);
}
};
@ -60,7 +58,7 @@ public:
} // namespace detail
} // namespace ipc
#else /*!WIN*/
#elif defined(IPC_OS_LINUX_)
#include "libipc/platform/waiter_linux.h"
@ -123,7 +121,7 @@ public:
class condition_impl : public object_impl<ipc::detail::condition> {
public:
bool wait(mutex_impl& mtx, std::size_t tm = invalid_value) {
bool wait(mutex_impl& mtx, std::uint64_t tm = invalid_value) {
return object().wait(mtx.object(), tm);
}
@ -168,7 +166,7 @@ public:
opened_.release();
}
bool wait(std::size_t tm = invalid_value) {
bool wait(std::uint64_t tm = invalid_value) {
return sem_helper::wait(h_, tm);
}
@ -179,8 +177,9 @@ public:
} // namespace detail
} // namespace ipc
#endif/*!WIN*/
#else/*linux*/
# error "Unsupported platform."
#endif
namespace ipc {
namespace detail {
@ -235,7 +234,7 @@ public:
}
template <typename F>
bool wait_if(F && pred, std::size_t tm = invalid_value) {
bool wait_if(F && pred, std::uint64_t tm = invalid_value) {
if (!valid()) return false;
return w_->wait_if(h_, &flags_, std::forward<F>(pred), tm);
}

View File

@ -3,6 +3,7 @@
#include <new>
#include <utility>
#include "libipc/platform/detail.h"
#include "libipc/utility/concept.h"
#include "libipc/pool_alloc.h"
@ -17,49 +18,45 @@ template <typename T, typename R = T*>
using IsImplUncomfortable = ipc::require<(sizeof(T) > sizeof(T*)), R>;
template <typename T, typename... P>
constexpr auto make_impl(P&&... params) -> IsImplComfortable<T> {
IPC_CONSTEXPR_ auto make_impl(P&&... params) -> IsImplComfortable<T> {
T* buf {};
::new (&buf) T { std::forward<P>(params)... };
return buf;
}
template <typename T>
constexpr auto impl(T* const (& p)) -> IsImplComfortable<T> {
IPC_CONSTEXPR_ auto impl(T* const (& p)) -> IsImplComfortable<T> {
return reinterpret_cast<T*>(&const_cast<char &>(reinterpret_cast<char const &>(p)));
}
template <typename T>
constexpr auto clear_impl(T* p) -> IsImplComfortable<T, void> {
IPC_CONSTEXPR_ auto clear_impl(T* p) -> IsImplComfortable<T, void> {
if (p != nullptr) impl(p)->~T();
}
template <typename T, typename... P>
constexpr auto make_impl(P&&... params) -> IsImplUncomfortable<T> {
IPC_CONSTEXPR_ auto make_impl(P&&... params) -> IsImplUncomfortable<T> {
return mem::alloc<T>(std::forward<P>(params)...);
}
template <typename T>
constexpr auto clear_impl(T* p) -> IsImplUncomfortable<T, void> {
IPC_CONSTEXPR_ auto clear_impl(T* p) -> IsImplUncomfortable<T, void> {
mem::free(p);
}
template <typename T>
constexpr auto impl(T* const (& p)) -> IsImplUncomfortable<T> {
IPC_CONSTEXPR_ auto impl(T* const (& p)) -> IsImplUncomfortable<T> {
return p;
}
template <typename T>
struct pimpl {
template <typename... P>
constexpr static T* make(P&&... params) {
IPC_CONSTEXPR_ static T* make(P&&... params) {
return make_impl<T>(std::forward<P>(params)...);
}
#if __cplusplus >= 201703L
constexpr void clear() {
#else /*__cplusplus < 201703L*/
void clear() {
#endif/*__cplusplus < 201703L*/
IPC_CONSTEXPR_ void clear() {
clear_impl(static_cast<T*>(const_cast<pimpl*>(this)));
}
};

View File

@ -24,7 +24,7 @@ struct waiter_helper {
};
template <typename Mutex, typename Ctrl, typename F>
static bool wait_if(Ctrl & ctrl, Mutex & mtx, F && pred, std::size_t tm) {
static bool wait_if(Ctrl & ctrl, Mutex & mtx, F && pred, std::uint64_t tm) {
auto & flags = ctrl.flags();
if (flags.is_closed_.load(std::memory_order_acquire)) {
return false;

70
src/mutex.cpp Normal file
View File

@ -0,0 +1,70 @@
#include "libipc/mutex.h"
#include "libipc/utility/pimpl.h"
#include "libipc/memory/resource.h"
#include "libipc/platform/detail.h"
#if defined(IPC_OS_WINDOWS_)
#include "libipc/platform/mutex_win.h"
#elif defined(IPC_OS_LINUX_)
#include "libipc/platform/mutex_linux.h"
#else/*linux*/
# error "Unsupported platform."
#endif
namespace ipc {
namespace sync {
class mutex::mutex_ : public ipc::pimpl<mutex_> {
public:
ipc::detail::sync::mutex lock_;
};
mutex::mutex()
: p_(p_->make()) {
}
mutex::mutex(char const * name)
: mutex() {
open(name);
}
mutex::~mutex() {
close();
p_->clear();
}
void const *mutex::native() const noexcept {
return impl(p_)->lock_.native();
}
void *mutex::native() noexcept {
return impl(p_)->lock_.native();
}
bool mutex::valid() const noexcept {
return impl(p_)->lock_.valid();
}
bool mutex::open(char const *name) noexcept {
return impl(p_)->lock_.open(name);
}
void mutex::close() noexcept {
impl(p_)->lock_.close();
}
bool mutex::lock(std::uint64_t tm) noexcept {
return impl(p_)->lock_.lock(tm);
}
bool mutex::try_lock() noexcept(false) {
return impl(p_)->lock_.try_lock();
}
bool mutex::unlock() noexcept {
return impl(p_)->lock_.unlock();
}
} // namespace sync
} // namespace ipc

View File

@ -47,18 +47,26 @@ handle& handle::operator=(handle rhs) {
return *this;
}
bool handle::valid() const {
bool handle::valid() const noexcept {
return impl(p_)->m_ != nullptr;
}
std::size_t handle::size() const {
std::size_t handle::size() const noexcept {
return impl(p_)->s_;
}
char const * handle::name() const {
char const * handle::name() const noexcept {
return impl(p_)->n_.c_str();
}
std::uint32_t handle::ref() const noexcept {
return shm::get_ref(impl(p_)->id_);
}
void handle::sub_ref() noexcept {
shm::sub_ref(impl(p_)->id_);
}
bool handle::acquire(char const * name, std::size_t size, unsigned mode) {
release();
impl(p_)->id_ = shm::acquire((impl(p_)->n_ = name).c_str(), size, mode);

View File

@ -44,7 +44,7 @@ bool mutex::unlock() {
#include "libipc/waiter_template.inc"
bool semaphore::wait(std::size_t tm) {
bool semaphore::wait(std::uint64_t tm) {
return impl(p_)->h_.wait(tm);
}
@ -62,7 +62,7 @@ bool semaphore::post(long count) {
#include "libipc/waiter_template.inc"
bool condition::wait(mutex& mtx, std::size_t tm) {
bool condition::wait(mutex& mtx, std::uint64_t tm) {
return impl(p_)->h_.wait(impl(mtx.p_)->h_, tm);
}

View File

@ -15,7 +15,10 @@ include_directories(
${LIBIPC_PROJECT_DIR}/3rdparty
${LIBIPC_PROJECT_DIR}/3rdparty/gtest/include)
file(GLOB SRC_FILES ${LIBIPC_PROJECT_DIR}/test/*.cpp)
file(GLOB SRC_FILES
${LIBIPC_PROJECT_DIR}/test/*.cpp
# ${LIBIPC_PROJECT_DIR}/test/profiler/*.cpp
)
file(GLOB HEAD_FILES ${LIBIPC_PROJECT_DIR}/test/*.h)
add_executable(${PROJECT_NAME} ${SRC_FILES} ${HEAD_FILES})

11
test/profiler/README.md Normal file
View File

@ -0,0 +1,11 @@
# A Quick Introduction to C++ Performance Tuning
(From: https://github.com/adah1972/cpp_summit_2020.git)
This repository contains the presentation file and example code for my
presentation at the C++ Summit 2020 held in Shenzhen, China on 45 December
2020.
The presentation content is shared under a [Creative Commons Attribution-Share
Alike 2.5 Licence](http://creativecommons.org/licenses/by-sa/2.5/). The code
is put in the public domain (i.e. do whatever you like with it), though an
acknowledgement will be appreciated (but not required).

View File

@ -0,0 +1,77 @@
#include "profiler.h"
#include <cassert>
#include <iostream>
#include <vector>
namespace {
struct profiling_data {
int number;
int call_count{};
uint64_t call_duration{};
};
class profiler {
public:
profiler();
~profiler();
void add_data(int number, uint64_t duration);
private:
std::vector<profiling_data> data_;
};
profiler::profiler()
{
size_t len = 0;
for (;;) {
if (name_map[len].name == NULL) {
break;
}
++len;
}
data_.resize(len);
int i = 0;
for (auto& item : data_) {
assert(i == name_map[i].number);
item.number = i;
++i;
}
}
profiler::~profiler()
{
#ifndef NDEBUG
for (auto& item : data_) {
if (item.call_count == 0) {
continue;
}
std::cout << item.number << " " << name_map[item.number].name
<< ":\n";
std::cout << " Call count: " << item.call_count << '\n';
std::cout << " Call duration: " << item.call_duration << '\n';
std::cout << " Average duration: "
<< item.call_duration * 1.0 /
(item.call_count != 0 ? item.call_count : 1)
<< '\n';
}
#endif
}
void profiler::add_data(int number, uint64_t duration)
{
assert(number >= 0 && number < static_cast<int>(data_.size()));
data_[number].call_count++;
data_[number].call_duration += duration;
}
profiler profiler_instance;
} // unnamed namespace
profiling_checker::~profiling_checker()
{
auto end_time = rdtsc();
profiler_instance.add_data(number_, end_time - start_time_);
}

35
test/profiler/profiler.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef PROFILER_H
#define PROFILER_H
#include "rdtsc.h"
struct name_mapper {
int number;
const char* name;
};
extern name_mapper name_map[];
class profiling_checker {
public:
profiling_checker(int number);
~profiling_checker();
private:
int number_;
uint64_t start_time_;
};
inline profiling_checker::profiling_checker(int number)
: number_(number)
{
start_time_ = rdtsc();
}
#ifdef NDEBUG
#define PROFILE_CHECK(func_number) (void)0
#else
#define PROFILE_CHECK(func_number) profiling_checker _checker(func_number)
#endif
#endif // PROFILER_H

52
test/profiler/rdtsc.h Normal file
View File

@ -0,0 +1,52 @@
#ifndef RDTSC_H
#define RDTSC_H
#include <stdint.h> // uint64_t
#if defined(_M_X64) || defined(_M_IX86) || defined(__x86_64) || defined(__i386)
# ifdef _WIN32
# include <intrin.h> // __rdtsc
# else
# include <x86intrin.h> // __rdtsc
# endif
# define HAS_HW_RDTSC 1
#else
# include <chrono> // std::chrono::high_resolution_clock
# define HAS_HW_RDTSC 0
#endif
inline uint64_t rdtsc()
{
#if HAS_HW_RDTSC
// _mm_lfence() might be used to serialize the instruction stream,
// and it would guarantee that RDTSC will not be reordered with
// other instructions. However, measurements show that the overhead
// may be too big (easily 15 to 30 CPU cycles) for profiling
// purposes: if reordering matters, the overhead matters too!
// Forbid the compiler from reordering instructions
# ifdef _MSC_VER
_ReadWriteBarrier();
# else
__asm__ __volatile__("" : : : "memory");
# endif
uint64_t result = __rdtsc();
// Forbid the compiler from reordering instructions
# ifdef _MSC_VER
_ReadWriteBarrier();
# else
__asm__ __volatile__("" : : : "memory");
# endif
return result;
#else
auto now = std::chrono::high_resolution_clock::now();
return std::chrono::duration_cast<std::chrono::nanoseconds>(
now.time_since_epoch())
.count();
#endif
}
#endif // RDTSC_H

42
test/test_pthread.cpp → test/test_sync.cpp Executable file → Normal file
View File

@ -1,10 +1,13 @@
#include <thread>
#include <iostream>
#include <mutex>
#include <chrono>
#include "test.h"
#if defined(__linux__) || defined(__linux)
#include "libipc/platform/detail.h"
#if defined(IPC_OS_LINUX_)
#include <pthread.h>
#include <time.h>
@ -29,8 +32,7 @@ TEST(PThread, Robust) {
pthread_mutex_unlock(&mutex);
pthread_mutex_destroy(&mutex);
}
#elif defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || \
defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
#elif defined(IPC_OS_WINDOWS_)
#include <Windows.h>
#include <tchar.h>
@ -46,4 +48,36 @@ TEST(PThread, Robust) {
CloseHandle(lock);
}
#endif // !__linux__
#endif // OS
#include "libipc/mutex.h"
TEST(Sync, Mutex) {
ipc::sync::mutex lock;
EXPECT_TRUE(lock.open("test-mutex-robust"));
std::thread{[] {
ipc::sync::mutex lock{"test-mutex-robust"};
EXPECT_TRUE(lock.valid());
EXPECT_TRUE(lock.lock());
}}.join();
EXPECT_THROW(lock.try_lock(), std::system_error);
int i = 0;
EXPECT_TRUE(lock.lock());
i = 100;
auto t2 = std::thread{[&i] {
ipc::sync::mutex lock{"test-mutex-robust"};
EXPECT_TRUE(lock.valid());
EXPECT_FALSE(lock.try_lock());
EXPECT_TRUE(lock.lock());
i += i;
EXPECT_TRUE(lock.unlock());
}};
std::this_thread::sleep_for(std::chrono::seconds(1));
EXPECT_EQ(i, 100);
EXPECT_TRUE(lock.unlock());
t2.join();
EXPECT_EQ(i, 200);
}