mirror of
https://github.com/mutouyun/cpp-ipc.git
synced 2026-01-01 03:12:13 +08:00
fix tls bugs (win); modify data structure
This commit is contained in:
parent
9bc6604faa
commit
6b7c561496
104
include/export.h
104
include/export.h
@ -1,52 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
|
||||
|
||||
# define IPC_DECL_EXPORT Q_DECL_EXPORT
|
||||
# define IPC_DECL_IMPORT Q_DECL_IMPORT
|
||||
|
||||
#else // defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
|
||||
|
||||
/*
|
||||
* Compiler & system detection for IPC_DECL_EXPORT & IPC_DECL_IMPORT.
|
||||
* Not using QtCore cause it shouldn't depend on Qt.
|
||||
*/
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# define IPC_DECL_EXPORT __declspec(dllexport)
|
||||
# define IPC_DECL_IMPORT __declspec(dllimport)
|
||||
#elif defined(__ARMCC__) || defined(__CC_ARM)
|
||||
# if defined(ANDROID) || defined(__linux__) || defined(__linux)
|
||||
# define IPC_DECL_EXPORT __attribute__((visibility("default")))
|
||||
# define IPC_DECL_IMPORT __attribute__((visibility("default")))
|
||||
# else
|
||||
# define IPC_DECL_EXPORT __declspec(dllexport)
|
||||
# define IPC_DECL_IMPORT __declspec(dllimport)
|
||||
# endif
|
||||
#elif defined(__GNUC__)
|
||||
# if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || \
|
||||
defined(WIN64) || defined(_WIN64) || defined(__WIN64__)
|
||||
# define IPC_DECL_EXPORT __declspec(dllexport)
|
||||
# define IPC_DECL_IMPORT __declspec(dllimport)
|
||||
# else
|
||||
# define IPC_DECL_EXPORT __attribute__((visibility("default")))
|
||||
# define IPC_DECL_IMPORT __attribute__((visibility("default")))
|
||||
# endif
|
||||
#else
|
||||
# define IPC_DECL_EXPORT __attribute__((visibility("default")))
|
||||
# define IPC_DECL_IMPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#endif // defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
|
||||
|
||||
/*
|
||||
* Define IPC_EXPORT for exporting function & class.
|
||||
*/
|
||||
|
||||
#ifndef IPC_EXPORT
|
||||
#if defined(__IPC_LIBRARY__)
|
||||
# define IPC_EXPORT IPC_DECL_EXPORT
|
||||
#else
|
||||
# define IPC_EXPORT IPC_DECL_IMPORT
|
||||
#endif
|
||||
#endif /*IPC_EXPORT*/
|
||||
#pragma once
|
||||
|
||||
#if defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
|
||||
|
||||
# define IPC_DECL_EXPORT Q_DECL_EXPORT
|
||||
# define IPC_DECL_IMPORT Q_DECL_IMPORT
|
||||
|
||||
#else // defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
|
||||
|
||||
/*
|
||||
* Compiler & system detection for IPC_DECL_EXPORT & IPC_DECL_IMPORT.
|
||||
* Not using QtCore cause it shouldn't depend on Qt.
|
||||
*/
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# define IPC_DECL_EXPORT __declspec(dllexport)
|
||||
# define IPC_DECL_IMPORT __declspec(dllimport)
|
||||
#elif defined(__ARMCC__) || defined(__CC_ARM)
|
||||
# if defined(ANDROID) || defined(__linux__) || defined(__linux)
|
||||
# define IPC_DECL_EXPORT __attribute__((visibility("default")))
|
||||
# define IPC_DECL_IMPORT __attribute__((visibility("default")))
|
||||
# else
|
||||
# define IPC_DECL_EXPORT __declspec(dllexport)
|
||||
# define IPC_DECL_IMPORT __declspec(dllimport)
|
||||
# endif
|
||||
#elif defined(__GNUC__)
|
||||
# if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || \
|
||||
defined(WIN64) || defined(_WIN64) || defined(__WIN64__)
|
||||
# define IPC_DECL_EXPORT __declspec(dllexport)
|
||||
# define IPC_DECL_IMPORT __declspec(dllimport)
|
||||
# else
|
||||
# define IPC_DECL_EXPORT __attribute__((visibility("default")))
|
||||
# define IPC_DECL_IMPORT __attribute__((visibility("default")))
|
||||
# endif
|
||||
#else
|
||||
# define IPC_DECL_EXPORT __attribute__((visibility("default")))
|
||||
# define IPC_DECL_IMPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#endif // defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
|
||||
|
||||
/*
|
||||
* Define IPC_EXPORT for exporting function & class.
|
||||
*/
|
||||
|
||||
#ifndef IPC_EXPORT
|
||||
#if defined(__IPC_LIBRARY__)
|
||||
# define IPC_EXPORT IPC_DECL_EXPORT
|
||||
#else
|
||||
# define IPC_EXPORT IPC_DECL_IMPORT
|
||||
#endif
|
||||
#endif /*IPC_EXPORT*/
|
||||
|
||||
302
include/ipc.h
302
include/ipc.h
@ -1,151 +1,151 @@
|
||||
#pragma once
|
||||
|
||||
#include "export.h"
|
||||
#include "def.h"
|
||||
#include "buffer.h"
|
||||
#include "shm.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ipc {
|
||||
|
||||
using handle_t = void*;
|
||||
using buff_t = buffer;
|
||||
|
||||
enum : unsigned {
|
||||
sender,
|
||||
receiver
|
||||
};
|
||||
|
||||
template <typename Flag>
|
||||
struct IPC_EXPORT chan_impl {
|
||||
static handle_t connect (char const * name, unsigned mode);
|
||||
static void disconnect(handle_t h);
|
||||
|
||||
static char const * name(handle_t h);
|
||||
|
||||
static std::size_t recv_count(handle_t h);
|
||||
static bool wait_for_recv(handle_t h, std::size_t r_count, std::size_t tm);
|
||||
|
||||
static bool send(handle_t h, void const * data, std::size_t size);
|
||||
static buff_t recv(handle_t h, std::size_t tm);
|
||||
|
||||
static bool try_send(handle_t h, void const * data, std::size_t size);
|
||||
static buff_t try_recv(handle_t h);
|
||||
};
|
||||
|
||||
template <typename Flag>
|
||||
class chan_wrapper {
|
||||
private:
|
||||
using detail_t = chan_impl<Flag>;
|
||||
handle_t h_ = nullptr;
|
||||
|
||||
public:
|
||||
chan_wrapper() = default;
|
||||
|
||||
explicit chan_wrapper(char const * name, unsigned mode = sender) {
|
||||
this->connect(name, mode);
|
||||
}
|
||||
|
||||
chan_wrapper(chan_wrapper&& rhs) {
|
||||
swap(rhs);
|
||||
}
|
||||
|
||||
~chan_wrapper() {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
void swap(chan_wrapper& rhs) {
|
||||
std::swap(h_, rhs.h_);
|
||||
}
|
||||
|
||||
chan_wrapper& operator=(chan_wrapper rhs) {
|
||||
swap(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
char const * name() const {
|
||||
return detail_t::name(h_);
|
||||
}
|
||||
|
||||
handle_t handle() const {
|
||||
return h_;
|
||||
}
|
||||
|
||||
bool valid() const {
|
||||
return (handle() != nullptr);
|
||||
}
|
||||
|
||||
chan_wrapper clone() const {
|
||||
return chan_wrapper { name() };
|
||||
}
|
||||
|
||||
bool connect(char const * name, unsigned mode = sender | receiver) {
|
||||
if (name == nullptr || name[0] == '\0') return false;
|
||||
this->disconnect();
|
||||
h_ = detail_t::connect(name, mode);
|
||||
return valid();
|
||||
}
|
||||
|
||||
void disconnect() {
|
||||
if (!valid()) return;
|
||||
detail_t::disconnect(h_);
|
||||
h_ = nullptr;
|
||||
}
|
||||
|
||||
std::size_t recv_count() const {
|
||||
return detail_t::recv_count(h_);
|
||||
}
|
||||
|
||||
bool wait_for_recv(std::size_t r_count, std::size_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) {
|
||||
return chan_wrapper(name).wait_for_recv(r_count, tm);
|
||||
}
|
||||
|
||||
bool send (void const * data, std::size_t size) { return detail_t::send(h_, data, size) ; }
|
||||
bool send (buff_t const & buff) { return this->send(buff.data(), buff.size()) ; }
|
||||
bool send (std::string const & str) { return this->send(str.c_str(), str.size() + 1); }
|
||||
|
||||
bool try_send(void const * data, std::size_t size) { return detail_t::try_send(h_, data, size) ; }
|
||||
bool try_send(buff_t const & buff) { return this->try_send(buff.data(), buff.size()) ; }
|
||||
bool try_send(std::string const & str) { return this->try_send(str.c_str(), str.size() + 1); }
|
||||
|
||||
buff_t recv(std::size_t tm = invalid_value) {
|
||||
return detail_t::recv(h_, tm);
|
||||
}
|
||||
|
||||
buff_t try_recv() {
|
||||
return detail_t::try_recv(h_);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Flag>
|
||||
using chan = chan_wrapper<Flag>;
|
||||
|
||||
/*
|
||||
* class route
|
||||
*
|
||||
* You could use one producer/server/sender for sending messages to a route,
|
||||
* then all the consumers/clients/receivers which are receiving with this route,
|
||||
* would receive your sent messages.
|
||||
*
|
||||
* A route could only be used in 1 to N
|
||||
* (one producer/writer to multi consumers/readers)
|
||||
*/
|
||||
|
||||
using route = chan<ipc::wr<relat::single, relat::multi, trans::broadcast>>;
|
||||
|
||||
/*
|
||||
* class channel
|
||||
*
|
||||
* You could use multi producers/writers for sending messages to a channel,
|
||||
* then all the consumers/readers which are receiving with this channel,
|
||||
* would receive your sent messages.
|
||||
*/
|
||||
|
||||
using channel = chan<ipc::wr<relat::multi, relat::multi, trans::broadcast>>;
|
||||
|
||||
} // namespace ipc
|
||||
#pragma once
|
||||
|
||||
#include "export.h"
|
||||
#include "def.h"
|
||||
#include "buffer.h"
|
||||
#include "shm.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ipc {
|
||||
|
||||
using handle_t = void*;
|
||||
using buff_t = buffer;
|
||||
|
||||
enum : unsigned {
|
||||
sender,
|
||||
receiver
|
||||
};
|
||||
|
||||
template <typename Flag>
|
||||
struct IPC_EXPORT chan_impl {
|
||||
static handle_t connect (char const * name, unsigned mode);
|
||||
static void disconnect(handle_t h);
|
||||
|
||||
static char const * name(handle_t h);
|
||||
|
||||
static std::size_t recv_count(handle_t h);
|
||||
static bool wait_for_recv(handle_t h, std::size_t r_count, std::size_t tm);
|
||||
|
||||
static bool send(handle_t h, void const * data, std::size_t size);
|
||||
static buff_t recv(handle_t h, std::size_t tm);
|
||||
|
||||
static bool try_send(handle_t h, void const * data, std::size_t size);
|
||||
static buff_t try_recv(handle_t h);
|
||||
};
|
||||
|
||||
template <typename Flag>
|
||||
class chan_wrapper {
|
||||
private:
|
||||
using detail_t = chan_impl<Flag>;
|
||||
handle_t h_ = nullptr;
|
||||
|
||||
public:
|
||||
chan_wrapper() = default;
|
||||
|
||||
explicit chan_wrapper(char const * name, unsigned mode = sender) {
|
||||
this->connect(name, mode);
|
||||
}
|
||||
|
||||
chan_wrapper(chan_wrapper&& rhs) {
|
||||
swap(rhs);
|
||||
}
|
||||
|
||||
~chan_wrapper() {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
void swap(chan_wrapper& rhs) {
|
||||
std::swap(h_, rhs.h_);
|
||||
}
|
||||
|
||||
chan_wrapper& operator=(chan_wrapper rhs) {
|
||||
swap(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
char const * name() const {
|
||||
return detail_t::name(h_);
|
||||
}
|
||||
|
||||
handle_t handle() const {
|
||||
return h_;
|
||||
}
|
||||
|
||||
bool valid() const {
|
||||
return (handle() != nullptr);
|
||||
}
|
||||
|
||||
chan_wrapper clone() const {
|
||||
return chan_wrapper { name() };
|
||||
}
|
||||
|
||||
bool connect(char const * name, unsigned mode = sender | receiver) {
|
||||
if (name == nullptr || name[0] == '\0') return false;
|
||||
this->disconnect();
|
||||
h_ = detail_t::connect(name, mode);
|
||||
return valid();
|
||||
}
|
||||
|
||||
void disconnect() {
|
||||
if (!valid()) return;
|
||||
detail_t::disconnect(h_);
|
||||
h_ = nullptr;
|
||||
}
|
||||
|
||||
std::size_t recv_count() const {
|
||||
return detail_t::recv_count(h_);
|
||||
}
|
||||
|
||||
bool wait_for_recv(std::size_t r_count, std::size_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) {
|
||||
return chan_wrapper(name).wait_for_recv(r_count, tm);
|
||||
}
|
||||
|
||||
bool send (void const * data, std::size_t size) { return detail_t::send(h_, data, size) ; }
|
||||
bool send (buff_t const & buff) { return this->send(buff.data(), buff.size()) ; }
|
||||
bool send (std::string const & str) { return this->send(str.c_str(), str.size() + 1); }
|
||||
|
||||
bool try_send(void const * data, std::size_t size) { return detail_t::try_send(h_, data, size) ; }
|
||||
bool try_send(buff_t const & buff) { return this->try_send(buff.data(), buff.size()) ; }
|
||||
bool try_send(std::string const & str) { return this->try_send(str.c_str(), str.size() + 1); }
|
||||
|
||||
buff_t recv(std::size_t tm = invalid_value) {
|
||||
return detail_t::recv(h_, tm);
|
||||
}
|
||||
|
||||
buff_t try_recv() {
|
||||
return detail_t::try_recv(h_);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Flag>
|
||||
using chan = chan_wrapper<Flag>;
|
||||
|
||||
/*
|
||||
* class route
|
||||
*
|
||||
* You could use one producer/server/sender for sending messages to a route,
|
||||
* then all the consumers/clients/receivers which are receiving with this route,
|
||||
* would receive your sent messages.
|
||||
*
|
||||
* A route could only be used in 1 to N
|
||||
* (one producer/writer to multi consumers/readers)
|
||||
*/
|
||||
|
||||
using route = chan<ipc::wr<relat::single, relat::multi, trans::broadcast>>;
|
||||
|
||||
/*
|
||||
* class channel
|
||||
*
|
||||
* You could use multi producers/writers for sending messages to a channel,
|
||||
* then all the consumers/readers which are receiving with this channel,
|
||||
* would receive your sent messages.
|
||||
*/
|
||||
|
||||
using channel = chan<ipc::wr<relat::multi, relat::multi, trans::broadcast>>;
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
@ -1,103 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include <new>
|
||||
#include <utility>
|
||||
|
||||
#include "export.h"
|
||||
#include "def.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
class IPC_EXPORT pool_alloc {
|
||||
public:
|
||||
static void clear();
|
||||
static void* alloc(std::size_t size);
|
||||
static void free(void* p, std::size_t size);
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// construct/destruct an object
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
struct impl {
|
||||
template <typename... P>
|
||||
static T* construct(T* p, P&&... params) {
|
||||
::new (p) T(std::forward<P>(params)...);
|
||||
return p;
|
||||
}
|
||||
|
||||
static void destruct(T* p) {
|
||||
reinterpret_cast<T*>(p)->~T();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, size_t N>
|
||||
struct impl<T[N]> {
|
||||
using type = T[N];
|
||||
|
||||
template <typename... P>
|
||||
static type* construct(type* p, P&&... params) {
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
impl<T>::construct(&((*p)[i]), std::forward<P>(params)...);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static void destruct(type* p) {
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
impl<T>::destruct(&((*p)[i]));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T, typename... P>
|
||||
T* construct(T* p, P&&... params) {
|
||||
return detail::impl<T>::construct(p, std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
template <typename T, typename... P>
|
||||
T* construct(void* p, P&&... params) {
|
||||
return construct(static_cast<T*>(p), std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void destruct(T* p) {
|
||||
return detail::impl<T>::destruct(p);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void destruct(void* p) {
|
||||
destruct(static_cast<T*>(p));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// general alloc/free
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
inline void* alloc(std::size_t size) {
|
||||
return pool_alloc::alloc(size);
|
||||
}
|
||||
|
||||
template <typename T, typename... P>
|
||||
T* alloc(P&&... params) {
|
||||
return construct<T>(pool_alloc::alloc(sizeof(T)), std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
inline void free(void* p, std::size_t size) {
|
||||
pool_alloc::free(p, size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void free(T* p) {
|
||||
destruct(p);
|
||||
pool_alloc::free(p, sizeof(T));
|
||||
}
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
#pragma once
|
||||
|
||||
#include <new>
|
||||
#include <utility>
|
||||
|
||||
#include "export.h"
|
||||
#include "def.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
class IPC_EXPORT pool_alloc {
|
||||
public:
|
||||
static void clear();
|
||||
static void* alloc(std::size_t size);
|
||||
static void free(void* p, std::size_t size);
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// construct/destruct an object
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
struct impl {
|
||||
template <typename... P>
|
||||
static T* construct(T* p, P&&... params) {
|
||||
::new (p) T(std::forward<P>(params)...);
|
||||
return p;
|
||||
}
|
||||
|
||||
static void destruct(T* p) {
|
||||
reinterpret_cast<T*>(p)->~T();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, size_t N>
|
||||
struct impl<T[N]> {
|
||||
using type = T[N];
|
||||
|
||||
template <typename... P>
|
||||
static type* construct(type* p, P&&... params) {
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
impl<T>::construct(&((*p)[i]), std::forward<P>(params)...);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static void destruct(type* p) {
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
impl<T>::destruct(&((*p)[i]));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T, typename... P>
|
||||
T* construct(T* p, P&&... params) {
|
||||
return detail::impl<T>::construct(p, std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
template <typename T, typename... P>
|
||||
T* construct(void* p, P&&... params) {
|
||||
return construct(static_cast<T*>(p), std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void destruct(T* p) {
|
||||
return detail::impl<T>::destruct(p);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void destruct(void* p) {
|
||||
destruct(static_cast<T*>(p));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// general alloc/free
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
inline void* alloc(std::size_t size) {
|
||||
return pool_alloc::alloc(size);
|
||||
}
|
||||
|
||||
template <typename T, typename... P>
|
||||
T* alloc(P&&... params) {
|
||||
return construct<T>(pool_alloc::alloc(sizeof(T)), std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
inline void free(void* p, std::size_t size) {
|
||||
pool_alloc::free(p, size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void free(T* p) {
|
||||
destruct(p);
|
||||
pool_alloc::free(p, sizeof(T));
|
||||
}
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
|
||||
@ -1,170 +1,170 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Gives hint to processor that improves performance of spin-wait loops.
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma push_macro("IPC_LOCK_PAUSE_")
|
||||
#undef IPC_LOCK_PAUSE_
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <windows.h> // YieldProcessor
|
||||
/*
|
||||
See: http://msdn.microsoft.com/en-us/library/windows/desktop/ms687419(v=vs.85).aspx
|
||||
Not for intel c++ compiler, so ignore http://software.intel.com/en-us/forums/topic/296168
|
||||
*/
|
||||
# define IPC_LOCK_PAUSE_() YieldProcessor()
|
||||
#elif defined(__GNUC__)
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
/*
|
||||
See: Intel(R) 64 and IA-32 Architectures Software Developer's Manual V2
|
||||
PAUSE-Spin Loop Hint, 4-57
|
||||
http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.html?wapkw=instruction+set+reference
|
||||
*/
|
||||
# define IPC_LOCK_PAUSE_() __asm__ __volatile__("pause")
|
||||
#elif defined(__ia64__) || defined(__ia64)
|
||||
/*
|
||||
See: Intel(R) Itanium(R) Architecture Developer's Manual, Vol.3
|
||||
hint - Performance Hint, 3:145
|
||||
http://www.intel.com/content/www/us/en/processors/itanium/itanium-architecture-vol-3-manual.html
|
||||
*/
|
||||
# define IPC_LOCK_PAUSE_() __asm__ __volatile__ ("hint @pause")
|
||||
#elif defined(__arm__)
|
||||
/*
|
||||
See: ARM Architecture Reference Manuals (YIELD)
|
||||
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.subset.architecture.reference/index.html
|
||||
*/
|
||||
# define IPC_LOCK_PAUSE_() __asm__ __volatile__ ("yield")
|
||||
#endif
|
||||
#endif/*compilers*/
|
||||
|
||||
#if !defined(IPC_LOCK_PAUSE_)
|
||||
/*
|
||||
Just use a compiler fence, prevent compiler from optimizing loop
|
||||
*/
|
||||
# define IPC_LOCK_PAUSE_() std::atomic_signal_fence(std::memory_order_seq_cst)
|
||||
#endif/*!defined(IPC_LOCK_PAUSE_)*/
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Yield to other threads
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace ipc {
|
||||
|
||||
template <typename K>
|
||||
inline void yield(K& k) noexcept {
|
||||
if (k < 4) { /* Do nothing */ }
|
||||
else
|
||||
if (k < 16) { IPC_LOCK_PAUSE_(); }
|
||||
else {
|
||||
std::this_thread::yield();
|
||||
return;
|
||||
}
|
||||
++k;
|
||||
}
|
||||
|
||||
template <std::size_t N = 4096, typename K, typename F>
|
||||
inline void sleep(K& k, F&& f) {
|
||||
if (k < static_cast<K>(N)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
else if (std::forward<F>(f)()) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
return;
|
||||
}
|
||||
++k;
|
||||
}
|
||||
|
||||
template <std::size_t N = 4096, typename K>
|
||||
inline void sleep(K& k) {
|
||||
sleep<N>(k, [] { return false; });
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
#pragma pop_macro("IPC_LOCK_PAUSE_")
|
||||
|
||||
namespace ipc {
|
||||
|
||||
class spin_lock {
|
||||
std::atomic<unsigned> lc_ { 0 };
|
||||
|
||||
public:
|
||||
void lock(void) noexcept {
|
||||
for (unsigned k = 0;
|
||||
lc_.exchange(1, std::memory_order_acquire);
|
||||
yield(k)) ;
|
||||
}
|
||||
|
||||
void unlock(void) noexcept {
|
||||
lc_.store(0, std::memory_order_release);
|
||||
}
|
||||
};
|
||||
|
||||
class rw_lock {
|
||||
using lc_ui_t = unsigned;
|
||||
|
||||
std::atomic<lc_ui_t> lc_ { 0 };
|
||||
|
||||
enum : lc_ui_t {
|
||||
w_mask = (std::numeric_limits<std::make_signed_t<lc_ui_t>>::max)(), // b 0111 1111
|
||||
w_flag = w_mask + 1 // b 1000 0000
|
||||
};
|
||||
|
||||
public:
|
||||
rw_lock() = default;
|
||||
|
||||
rw_lock(const rw_lock&) = delete;
|
||||
rw_lock& operator=(const rw_lock&) = delete;
|
||||
rw_lock(rw_lock&&) = delete;
|
||||
rw_lock& operator=(rw_lock&&) = delete;
|
||||
|
||||
void lock() noexcept {
|
||||
for (unsigned k = 0;;) {
|
||||
auto old = lc_.fetch_or(w_flag, std::memory_order_acq_rel);
|
||||
if (!old) return; // got w-lock
|
||||
if (!(old & w_flag)) break; // other thread having r-lock
|
||||
yield(k); // other thread having w-lock
|
||||
}
|
||||
// wait for reading finished
|
||||
for (unsigned k = 0;
|
||||
lc_.load(std::memory_order_acquire) & w_mask;
|
||||
yield(k)) ;
|
||||
}
|
||||
|
||||
void unlock() noexcept {
|
||||
lc_.store(0, std::memory_order_release);
|
||||
}
|
||||
|
||||
void lock_shared() noexcept {
|
||||
auto old = lc_.load(std::memory_order_acquire);
|
||||
for (unsigned k = 0;;) {
|
||||
// if w_flag set, just continue
|
||||
if (old & w_flag) {
|
||||
yield(k);
|
||||
old = lc_.load(std::memory_order_acquire);
|
||||
}
|
||||
// otherwise try cas lc + 1 (set r-lock)
|
||||
else if (lc_.compare_exchange_weak(old, old + 1, std::memory_order_release)) {
|
||||
return;
|
||||
}
|
||||
// set r-lock failed, old has been updated
|
||||
}
|
||||
}
|
||||
|
||||
void unlock_shared() noexcept {
|
||||
lc_.fetch_sub(1, std::memory_order_release);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Gives hint to processor that improves performance of spin-wait loops.
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma push_macro("IPC_LOCK_PAUSE_")
|
||||
#undef IPC_LOCK_PAUSE_
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <windows.h> // YieldProcessor
|
||||
/*
|
||||
See: http://msdn.microsoft.com/en-us/library/windows/desktop/ms687419(v=vs.85).aspx
|
||||
Not for intel c++ compiler, so ignore http://software.intel.com/en-us/forums/topic/296168
|
||||
*/
|
||||
# define IPC_LOCK_PAUSE_() YieldProcessor()
|
||||
#elif defined(__GNUC__)
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
/*
|
||||
See: Intel(R) 64 and IA-32 Architectures Software Developer's Manual V2
|
||||
PAUSE-Spin Loop Hint, 4-57
|
||||
http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.html?wapkw=instruction+set+reference
|
||||
*/
|
||||
# define IPC_LOCK_PAUSE_() __asm__ __volatile__("pause")
|
||||
#elif defined(__ia64__) || defined(__ia64)
|
||||
/*
|
||||
See: Intel(R) Itanium(R) Architecture Developer's Manual, Vol.3
|
||||
hint - Performance Hint, 3:145
|
||||
http://www.intel.com/content/www/us/en/processors/itanium/itanium-architecture-vol-3-manual.html
|
||||
*/
|
||||
# define IPC_LOCK_PAUSE_() __asm__ __volatile__ ("hint @pause")
|
||||
#elif defined(__arm__)
|
||||
/*
|
||||
See: ARM Architecture Reference Manuals (YIELD)
|
||||
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.subset.architecture.reference/index.html
|
||||
*/
|
||||
# define IPC_LOCK_PAUSE_() __asm__ __volatile__ ("yield")
|
||||
#endif
|
||||
#endif/*compilers*/
|
||||
|
||||
#if !defined(IPC_LOCK_PAUSE_)
|
||||
/*
|
||||
Just use a compiler fence, prevent compiler from optimizing loop
|
||||
*/
|
||||
# define IPC_LOCK_PAUSE_() std::atomic_signal_fence(std::memory_order_seq_cst)
|
||||
#endif/*!defined(IPC_LOCK_PAUSE_)*/
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Yield to other threads
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace ipc {
|
||||
|
||||
template <typename K>
|
||||
inline void yield(K& k) noexcept {
|
||||
if (k < 4) { /* Do nothing */ }
|
||||
else
|
||||
if (k < 16) { IPC_LOCK_PAUSE_(); }
|
||||
else {
|
||||
std::this_thread::yield();
|
||||
return;
|
||||
}
|
||||
++k;
|
||||
}
|
||||
|
||||
template <std::size_t N = 4096, typename K, typename F>
|
||||
inline void sleep(K& k, F&& f) {
|
||||
if (k < static_cast<K>(N)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
else if (std::forward<F>(f)()) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
return;
|
||||
}
|
||||
++k;
|
||||
}
|
||||
|
||||
template <std::size_t N = 4096, typename K>
|
||||
inline void sleep(K& k) {
|
||||
sleep<N>(k, [] { return false; });
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
#pragma pop_macro("IPC_LOCK_PAUSE_")
|
||||
|
||||
namespace ipc {
|
||||
|
||||
class spin_lock {
|
||||
std::atomic<unsigned> lc_ { 0 };
|
||||
|
||||
public:
|
||||
void lock(void) noexcept {
|
||||
for (unsigned k = 0;
|
||||
lc_.exchange(1, std::memory_order_acquire);
|
||||
yield(k)) ;
|
||||
}
|
||||
|
||||
void unlock(void) noexcept {
|
||||
lc_.store(0, std::memory_order_release);
|
||||
}
|
||||
};
|
||||
|
||||
class rw_lock {
|
||||
using lc_ui_t = unsigned;
|
||||
|
||||
std::atomic<lc_ui_t> lc_ { 0 };
|
||||
|
||||
enum : lc_ui_t {
|
||||
w_mask = (std::numeric_limits<std::make_signed_t<lc_ui_t>>::max)(), // b 0111 1111
|
||||
w_flag = w_mask + 1 // b 1000 0000
|
||||
};
|
||||
|
||||
public:
|
||||
rw_lock() = default;
|
||||
|
||||
rw_lock(const rw_lock&) = delete;
|
||||
rw_lock& operator=(const rw_lock&) = delete;
|
||||
rw_lock(rw_lock&&) = delete;
|
||||
rw_lock& operator=(rw_lock&&) = delete;
|
||||
|
||||
void lock() noexcept {
|
||||
for (unsigned k = 0;;) {
|
||||
auto old = lc_.fetch_or(w_flag, std::memory_order_acq_rel);
|
||||
if (!old) return; // got w-lock
|
||||
if (!(old & w_flag)) break; // other thread having r-lock
|
||||
yield(k); // other thread having w-lock
|
||||
}
|
||||
// wait for reading finished
|
||||
for (unsigned k = 0;
|
||||
lc_.load(std::memory_order_acquire) & w_mask;
|
||||
yield(k)) ;
|
||||
}
|
||||
|
||||
void unlock() noexcept {
|
||||
lc_.store(0, std::memory_order_release);
|
||||
}
|
||||
|
||||
void lock_shared() noexcept {
|
||||
auto old = lc_.load(std::memory_order_acquire);
|
||||
for (unsigned k = 0;;) {
|
||||
// if w_flag set, just continue
|
||||
if (old & w_flag) {
|
||||
yield(k);
|
||||
old = lc_.load(std::memory_order_acquire);
|
||||
}
|
||||
// otherwise try cas lc + 1 (set r-lock)
|
||||
else if (lc_.compare_exchange_weak(old, old + 1, std::memory_order_release)) {
|
||||
return;
|
||||
}
|
||||
// set r-lock failed, old has been updated
|
||||
}
|
||||
}
|
||||
|
||||
void unlock_shared() noexcept {
|
||||
lc_.fetch_sub(1, std::memory_order_release);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
104
include/shm.h
104
include/shm.h
@ -1,52 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "export.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace shm {
|
||||
|
||||
using id_t = void*;
|
||||
|
||||
enum : unsigned {
|
||||
create = 0x01,
|
||||
open = 0x02
|
||||
};
|
||||
|
||||
IPC_EXPORT id_t acquire(char const * name, std::size_t size, unsigned mode = create | open);
|
||||
IPC_EXPORT void * get_mem(id_t id, std::size_t * size);
|
||||
IPC_EXPORT void release(id_t id);
|
||||
IPC_EXPORT void remove (id_t id);
|
||||
IPC_EXPORT void remove (char const * name);
|
||||
|
||||
class IPC_EXPORT handle {
|
||||
public:
|
||||
handle();
|
||||
handle(char const * name, std::size_t size, unsigned mode = create | open);
|
||||
handle(handle&& rhs);
|
||||
|
||||
~handle();
|
||||
|
||||
void swap(handle& rhs);
|
||||
handle& operator=(handle rhs);
|
||||
|
||||
bool valid() const;
|
||||
std::size_t size () const;
|
||||
char const * name () const;
|
||||
|
||||
bool acquire(char const * name, std::size_t size, unsigned mode = create | open);
|
||||
void release();
|
||||
|
||||
void* get() const;
|
||||
|
||||
void attach(id_t);
|
||||
id_t detach();
|
||||
|
||||
private:
|
||||
class handle_;
|
||||
handle_* p_;
|
||||
};
|
||||
|
||||
} // namespace shm
|
||||
} // namespace ipc
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "export.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace shm {
|
||||
|
||||
using id_t = void*;
|
||||
|
||||
enum : unsigned {
|
||||
create = 0x01,
|
||||
open = 0x02
|
||||
};
|
||||
|
||||
IPC_EXPORT id_t acquire(char const * name, std::size_t size, unsigned mode = create | open);
|
||||
IPC_EXPORT void * get_mem(id_t id, std::size_t * size);
|
||||
IPC_EXPORT void release(id_t id);
|
||||
IPC_EXPORT void remove (id_t id);
|
||||
IPC_EXPORT void remove (char const * name);
|
||||
|
||||
class IPC_EXPORT handle {
|
||||
public:
|
||||
handle();
|
||||
handle(char const * name, std::size_t size, unsigned mode = create | open);
|
||||
handle(handle&& rhs);
|
||||
|
||||
~handle();
|
||||
|
||||
void swap(handle& rhs);
|
||||
handle& operator=(handle rhs);
|
||||
|
||||
bool valid() const;
|
||||
std::size_t size () const;
|
||||
char const * name () const;
|
||||
|
||||
bool acquire(char const * name, std::size_t size, unsigned mode = create | open);
|
||||
void release();
|
||||
|
||||
void* get() const;
|
||||
|
||||
void attach(id_t);
|
||||
id_t detach();
|
||||
|
||||
private:
|
||||
class handle_;
|
||||
handle_* p_;
|
||||
};
|
||||
|
||||
} // namespace shm
|
||||
} // namespace ipc
|
||||
|
||||
@ -1,86 +1,90 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include <limits>
|
||||
|
||||
#include "export.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace tls {
|
||||
|
||||
using key_t = std::uint64_t;
|
||||
using destructor_t = void (*)(void*);
|
||||
|
||||
enum : key_t {
|
||||
invalid_value = (std::numeric_limits<key_t>::max)()
|
||||
};
|
||||
|
||||
IPC_EXPORT key_t create (destructor_t destructor = nullptr);
|
||||
IPC_EXPORT void release(key_t key);
|
||||
|
||||
IPC_EXPORT bool set(key_t key, void* ptr);
|
||||
IPC_EXPORT void* get(key_t key);
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Thread-local pointer
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
<Remarks>
|
||||
|
||||
1. In Windows, if you do not compile thread_local_ptr.cpp,
|
||||
use thread_local_ptr will cause memory leaks.
|
||||
|
||||
2. You need to set the thread_local_ptr's storage manually:
|
||||
```
|
||||
thread_local_ptr<int> p;
|
||||
if (!p) p = new int(123);
|
||||
```
|
||||
Just like an ordinary pointer. Or you could just call create:
|
||||
```
|
||||
thread_local_ptr<int> p;
|
||||
p.create(123);
|
||||
```
|
||||
*/
|
||||
|
||||
template <typename T>
|
||||
class pointer {
|
||||
key_t key_;
|
||||
|
||||
public:
|
||||
using value_type = T;
|
||||
|
||||
pointer() {
|
||||
key_ = tls::create([](void* p) { delete static_cast<T*>(p); });
|
||||
}
|
||||
|
||||
~pointer() {
|
||||
tls::release(key_);
|
||||
}
|
||||
|
||||
template <typename... P>
|
||||
T* create(P&&... params) {
|
||||
thread_local auto ptr = static_cast<T*>(*this);
|
||||
if (ptr == nullptr) {
|
||||
return ptr = (*this) = new T(std::forward<P>(params)...);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
T* operator=(T* ptr) {
|
||||
set(key_, ptr);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
operator T*() const { return static_cast<T*>(get(key_)); }
|
||||
|
||||
T& operator* () { return *static_cast<T*>(*this); }
|
||||
const T& operator* () const { return *static_cast<T*>(*this); }
|
||||
|
||||
T* operator->() { return static_cast<T*>(*this); }
|
||||
const T* operator->() const { return static_cast<T*>(*this); }
|
||||
};
|
||||
|
||||
} // namespace tls
|
||||
} // namespace ipc
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include <limits>
|
||||
|
||||
#include "export.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace tls {
|
||||
|
||||
using key_t = std::uint64_t;
|
||||
using destructor_t = void (*)(void*);
|
||||
|
||||
enum : key_t {
|
||||
invalid_value = (std::numeric_limits<key_t>::max)()
|
||||
};
|
||||
|
||||
IPC_EXPORT key_t create (destructor_t destructor = nullptr);
|
||||
IPC_EXPORT void release(key_t key);
|
||||
|
||||
IPC_EXPORT bool set(key_t key, void* ptr);
|
||||
IPC_EXPORT void* get(key_t key);
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Thread-local pointer
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
* <Remarks>
|
||||
*
|
||||
* 1. In Windows, if you do not compile thread_local_ptr.cpp,
|
||||
* use thread_local_ptr will cause memory leaks.
|
||||
*
|
||||
* 2. You need to set the thread_local_ptr's storage manually:
|
||||
* ```
|
||||
* tls::pointer<int> p;
|
||||
* if (!p) p = new int(123);
|
||||
* ```
|
||||
* Just like an ordinary pointer. Or you could just call create:
|
||||
* ```
|
||||
* tls::pointer<int> p;
|
||||
* p.create(123);
|
||||
* ```
|
||||
*/
|
||||
|
||||
template <typename T>
|
||||
class pointer {
|
||||
key_t key_;
|
||||
|
||||
public:
|
||||
using value_type = T;
|
||||
|
||||
pointer()
|
||||
: key_(tls::create([](void* p) { delete static_cast<T*>(p); })) {
|
||||
}
|
||||
|
||||
~pointer() {
|
||||
tls::release(key_);
|
||||
}
|
||||
|
||||
template <typename... P>
|
||||
T* create(P&&... params) {
|
||||
thread_local auto ptr = static_cast<T*>(get(key_));
|
||||
if (ptr == nullptr) {
|
||||
ptr = new T(std::forward<P>(params)...);
|
||||
if (!set(key_, ptr)) {
|
||||
delete ptr;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
T* operator=(T* ptr) {
|
||||
set(key_, ptr);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
operator T*() const { return static_cast<T*>(get(key_)); }
|
||||
|
||||
T& operator* () { return *static_cast<T*>(*this); }
|
||||
const T& operator* () const { return *static_cast<T*>(*this); }
|
||||
|
||||
T* operator->() { return static_cast<T*>(*this); }
|
||||
const T* operator->() const { return static_cast<T*>(*this); }
|
||||
};
|
||||
|
||||
} // namespace tls
|
||||
} // namespace ipc
|
||||
|
||||
186
include/waiter.h
186
include/waiter.h
@ -1,93 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#include "export.h"
|
||||
#include "def.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
class condition;
|
||||
class IPC_EXPORT mutex {
|
||||
public:
|
||||
mutex();
|
||||
explicit mutex(char const * name);
|
||||
mutex(mutex&& rhs);
|
||||
|
||||
~mutex();
|
||||
|
||||
static void remove(char const * name);
|
||||
|
||||
void swap(mutex& rhs);
|
||||
mutex& operator=(mutex rhs);
|
||||
|
||||
bool valid() const;
|
||||
char const * name () const;
|
||||
|
||||
bool open (char const * name);
|
||||
void close();
|
||||
|
||||
bool lock ();
|
||||
bool unlock();
|
||||
|
||||
private:
|
||||
class mutex_;
|
||||
mutex_* p_;
|
||||
|
||||
friend class condition;
|
||||
};
|
||||
|
||||
class IPC_EXPORT semaphore {
|
||||
public:
|
||||
semaphore();
|
||||
explicit semaphore(char const * name);
|
||||
semaphore(semaphore&& rhs);
|
||||
|
||||
~semaphore();
|
||||
|
||||
static void remove(char const * name);
|
||||
|
||||
void swap(semaphore& rhs);
|
||||
semaphore& operator=(semaphore rhs);
|
||||
|
||||
bool valid() const;
|
||||
char const * name () const;
|
||||
|
||||
bool open (char const * name, long count = 0);
|
||||
void close();
|
||||
|
||||
bool wait(std::size_t tm = invalid_value);
|
||||
bool post(long count = 1);
|
||||
|
||||
private:
|
||||
class semaphore_;
|
||||
semaphore_* p_;
|
||||
};
|
||||
|
||||
class IPC_EXPORT condition {
|
||||
public:
|
||||
condition();
|
||||
explicit condition(char const * name);
|
||||
condition(condition&& rhs);
|
||||
|
||||
~condition();
|
||||
|
||||
static void remove(char const * name);
|
||||
|
||||
void swap(condition& rhs);
|
||||
condition& operator=(condition rhs);
|
||||
|
||||
bool valid() const;
|
||||
char const * name () const;
|
||||
|
||||
bool open (char const * name);
|
||||
void close();
|
||||
|
||||
bool wait(mutex&, std::size_t tm = invalid_value);
|
||||
bool notify();
|
||||
bool broadcast();
|
||||
|
||||
private:
|
||||
class condition_;
|
||||
condition_* p_;
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
#pragma once
|
||||
|
||||
#include "export.h"
|
||||
#include "def.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
class condition;
|
||||
class IPC_EXPORT mutex {
|
||||
public:
|
||||
mutex();
|
||||
explicit mutex(char const * name);
|
||||
mutex(mutex&& rhs);
|
||||
|
||||
~mutex();
|
||||
|
||||
static void remove(char const * name);
|
||||
|
||||
void swap(mutex& rhs);
|
||||
mutex& operator=(mutex rhs);
|
||||
|
||||
bool valid() const;
|
||||
char const * name () const;
|
||||
|
||||
bool open (char const * name);
|
||||
void close();
|
||||
|
||||
bool lock ();
|
||||
bool unlock();
|
||||
|
||||
private:
|
||||
class mutex_;
|
||||
mutex_* p_;
|
||||
|
||||
friend class condition;
|
||||
};
|
||||
|
||||
class IPC_EXPORT semaphore {
|
||||
public:
|
||||
semaphore();
|
||||
explicit semaphore(char const * name);
|
||||
semaphore(semaphore&& rhs);
|
||||
|
||||
~semaphore();
|
||||
|
||||
static void remove(char const * name);
|
||||
|
||||
void swap(semaphore& rhs);
|
||||
semaphore& operator=(semaphore rhs);
|
||||
|
||||
bool valid() const;
|
||||
char const * name () const;
|
||||
|
||||
bool open (char const * name, long count = 0);
|
||||
void close();
|
||||
|
||||
bool wait(std::size_t tm = invalid_value);
|
||||
bool post(long count = 1);
|
||||
|
||||
private:
|
||||
class semaphore_;
|
||||
semaphore_* p_;
|
||||
};
|
||||
|
||||
class IPC_EXPORT condition {
|
||||
public:
|
||||
condition();
|
||||
explicit condition(char const * name);
|
||||
condition(condition&& rhs);
|
||||
|
||||
~condition();
|
||||
|
||||
static void remove(char const * name);
|
||||
|
||||
void swap(condition& rhs);
|
||||
condition& operator=(condition rhs);
|
||||
|
||||
bool valid() const;
|
||||
char const * name () const;
|
||||
|
||||
bool open (char const * name);
|
||||
void close();
|
||||
|
||||
bool wait(mutex&, std::size_t tm = invalid_value);
|
||||
bool notify();
|
||||
bool broadcast();
|
||||
|
||||
private:
|
||||
class condition_;
|
||||
condition_* p_;
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
166
src/buffer.cpp
166
src/buffer.cpp
@ -1,83 +1,83 @@
|
||||
#include "buffer.h"
|
||||
#include "pimpl.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace ipc {
|
||||
|
||||
bool operator==(buffer const & b1, buffer const & b2) {
|
||||
return (b1.size() == b2.size()) && (std::memcmp(b1.data(), b2.data(), b1.size()) == 0);
|
||||
}
|
||||
|
||||
class buffer::buffer_ : public pimpl<buffer_> {
|
||||
public:
|
||||
void* p_;
|
||||
std::size_t s_;
|
||||
void* a_;
|
||||
buffer::destructor_t d_;
|
||||
|
||||
buffer_(void* p, std::size_t s, buffer::destructor_t d, void* a)
|
||||
: p_(p), s_(s), a_(a), d_(d) {
|
||||
}
|
||||
|
||||
~buffer_() {
|
||||
if (d_ == nullptr) return;
|
||||
d_((a_ == nullptr) ? p_ : a_, s_);
|
||||
}
|
||||
};
|
||||
|
||||
buffer::buffer()
|
||||
: buffer(nullptr, 0, nullptr, nullptr) {
|
||||
}
|
||||
|
||||
buffer::buffer(void* p, std::size_t s, destructor_t d)
|
||||
: p_(p_->make(p, s, d, nullptr)) {
|
||||
}
|
||||
|
||||
buffer::buffer(void* p, std::size_t s, destructor_t d, void* additional)
|
||||
: p_(p_->make(p, s, d, additional)) {
|
||||
}
|
||||
|
||||
buffer::buffer(void* p, std::size_t s)
|
||||
: buffer(p, s, nullptr) {
|
||||
}
|
||||
|
||||
buffer::buffer(char const & c)
|
||||
: buffer(const_cast<char*>(&c), 1) {
|
||||
}
|
||||
|
||||
buffer::buffer(buffer&& rhs)
|
||||
: buffer() {
|
||||
swap(rhs);
|
||||
}
|
||||
|
||||
buffer::~buffer() {
|
||||
p_->clear();
|
||||
}
|
||||
|
||||
void buffer::swap(buffer& rhs) {
|
||||
std::swap(p_, rhs.p_);
|
||||
}
|
||||
|
||||
buffer& buffer::operator=(buffer rhs) {
|
||||
swap(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool buffer::empty() const noexcept {
|
||||
return (impl(p_)->p_ == nullptr) || (impl(p_)->s_ == 0);
|
||||
}
|
||||
|
||||
void* buffer::data() noexcept {
|
||||
return impl(p_)->p_;
|
||||
}
|
||||
|
||||
void const * buffer::data() const noexcept {
|
||||
return impl(p_)->p_;
|
||||
}
|
||||
|
||||
std::size_t buffer::size() const noexcept {
|
||||
return impl(p_)->s_;
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
#include "buffer.h"
|
||||
#include "pimpl.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace ipc {
|
||||
|
||||
bool operator==(buffer const & b1, buffer const & b2) {
|
||||
return (b1.size() == b2.size()) && (std::memcmp(b1.data(), b2.data(), b1.size()) == 0);
|
||||
}
|
||||
|
||||
class buffer::buffer_ : public pimpl<buffer_> {
|
||||
public:
|
||||
void* p_;
|
||||
std::size_t s_;
|
||||
void* a_;
|
||||
buffer::destructor_t d_;
|
||||
|
||||
buffer_(void* p, std::size_t s, buffer::destructor_t d, void* a)
|
||||
: p_(p), s_(s), a_(a), d_(d) {
|
||||
}
|
||||
|
||||
~buffer_() {
|
||||
if (d_ == nullptr) return;
|
||||
d_((a_ == nullptr) ? p_ : a_, s_);
|
||||
}
|
||||
};
|
||||
|
||||
buffer::buffer()
|
||||
: buffer(nullptr, 0, nullptr, nullptr) {
|
||||
}
|
||||
|
||||
buffer::buffer(void* p, std::size_t s, destructor_t d)
|
||||
: p_(p_->make(p, s, d, nullptr)) {
|
||||
}
|
||||
|
||||
buffer::buffer(void* p, std::size_t s, destructor_t d, void* additional)
|
||||
: p_(p_->make(p, s, d, additional)) {
|
||||
}
|
||||
|
||||
buffer::buffer(void* p, std::size_t s)
|
||||
: buffer(p, s, nullptr) {
|
||||
}
|
||||
|
||||
buffer::buffer(char const & c)
|
||||
: buffer(const_cast<char*>(&c), 1) {
|
||||
}
|
||||
|
||||
buffer::buffer(buffer&& rhs)
|
||||
: buffer() {
|
||||
swap(rhs);
|
||||
}
|
||||
|
||||
buffer::~buffer() {
|
||||
p_->clear();
|
||||
}
|
||||
|
||||
void buffer::swap(buffer& rhs) {
|
||||
std::swap(p_, rhs.p_);
|
||||
}
|
||||
|
||||
buffer& buffer::operator=(buffer rhs) {
|
||||
swap(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool buffer::empty() const noexcept {
|
||||
return (impl(p_)->p_ == nullptr) || (impl(p_)->s_ == 0);
|
||||
}
|
||||
|
||||
void* buffer::data() noexcept {
|
||||
return impl(p_)->p_;
|
||||
}
|
||||
|
||||
void const * buffer::data() const noexcept {
|
||||
return impl(p_)->p_;
|
||||
}
|
||||
|
||||
std::size_t buffer::size() const noexcept {
|
||||
return impl(p_)->s_;
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
@ -1,60 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
|
||||
#include "def.h"
|
||||
|
||||
#include "circ/elem_def.h"
|
||||
#include "platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace circ {
|
||||
|
||||
template <typename Policy,
|
||||
std::size_t DataSize,
|
||||
std::size_t AlignSize = (ipc::detail::min)(DataSize, alignof(std::max_align_t))>
|
||||
class elem_array : public ipc::circ::conn_head {
|
||||
public:
|
||||
using base_t = ipc::circ::conn_head;
|
||||
using policy_t = Policy;
|
||||
using cursor_t = decltype(std::declval<policy_t>().cursor());
|
||||
using elem_t = typename policy_t::template elem_t<DataSize, AlignSize>;
|
||||
|
||||
enum : std::size_t {
|
||||
head_size = sizeof(base_t) + sizeof(policy_t),
|
||||
data_size = DataSize,
|
||||
elem_max = (std::numeric_limits<uint_t<8>>::max)() + 1, // default is 255 + 1
|
||||
elem_size = sizeof(elem_t),
|
||||
block_size = elem_size * elem_max
|
||||
};
|
||||
|
||||
private:
|
||||
policy_t head_;
|
||||
elem_t block_[elem_max] {};
|
||||
|
||||
public:
|
||||
cursor_t cursor() const noexcept {
|
||||
return head_.cursor();
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool push(F&& f) {
|
||||
return head_.push(this, std::forward<F>(f), block_);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool force_push(F&& f) {
|
||||
return head_.force_push(this, std::forward<F>(f), block_);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool pop(cursor_t* cur, F&& f) {
|
||||
if (cur == nullptr) return false;
|
||||
return head_.pop(this, *cur, std::forward<F>(f), block_);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace circ
|
||||
} // namespace ipc
|
||||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
|
||||
#include "def.h"
|
||||
|
||||
#include "circ/elem_def.h"
|
||||
#include "platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace circ {
|
||||
|
||||
template <typename Policy,
|
||||
std::size_t DataSize,
|
||||
std::size_t AlignSize = (ipc::detail::min)(DataSize, alignof(std::max_align_t))>
|
||||
class elem_array : public ipc::circ::conn_head {
|
||||
public:
|
||||
using base_t = ipc::circ::conn_head;
|
||||
using policy_t = Policy;
|
||||
using cursor_t = decltype(std::declval<policy_t>().cursor());
|
||||
using elem_t = typename policy_t::template elem_t<DataSize, AlignSize>;
|
||||
|
||||
enum : std::size_t {
|
||||
head_size = sizeof(base_t) + sizeof(policy_t),
|
||||
data_size = DataSize,
|
||||
elem_max = (std::numeric_limits<uint_t<8>>::max)() + 1, // default is 255 + 1
|
||||
elem_size = sizeof(elem_t),
|
||||
block_size = elem_size * elem_max
|
||||
};
|
||||
|
||||
private:
|
||||
policy_t head_;
|
||||
elem_t block_[elem_max] {};
|
||||
|
||||
public:
|
||||
cursor_t cursor() const noexcept {
|
||||
return head_.cursor();
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool push(F&& f) {
|
||||
return head_.push(this, std::forward<F>(f), block_);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool force_push(F&& f) {
|
||||
return head_.force_push(this, std::forward<F>(f), block_);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool pop(cursor_t* cur, F&& f) {
|
||||
if (cur == nullptr) return false;
|
||||
return head_.pop(this, *cur, std::forward<F>(f), block_);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace circ
|
||||
} // namespace ipc
|
||||
|
||||
@ -1,75 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <new>
|
||||
|
||||
#include "rw_lock.h"
|
||||
|
||||
#include "platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace circ {
|
||||
|
||||
using u1_t = ipc::uint_t<8>;
|
||||
using u2_t = ipc::uint_t<32>;
|
||||
|
||||
constexpr u1_t index_of(u2_t c) noexcept {
|
||||
return static_cast<u1_t>(c);
|
||||
}
|
||||
|
||||
class conn_head {
|
||||
std::atomic<std::size_t> cc_ { 0 }; // connection counter
|
||||
|
||||
ipc::spin_lock lc_;
|
||||
std::atomic<bool> constructed_;
|
||||
|
||||
std::atomic<bool> dis_flag_;
|
||||
|
||||
public:
|
||||
void init() {
|
||||
/* DCLP */
|
||||
if (!constructed_.load(std::memory_order_acquire)) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lc_);
|
||||
if (!constructed_.load(std::memory_order_relaxed)) {
|
||||
::new (this) conn_head;
|
||||
constructed_.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
conn_head() = default;
|
||||
conn_head(const conn_head&) = delete;
|
||||
conn_head& operator=(const conn_head&) = delete;
|
||||
|
||||
std::size_t connect() noexcept {
|
||||
return cc_.fetch_add(1, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
std::size_t disconnect() noexcept {
|
||||
return cc_.fetch_sub(1, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
void try_disconnect() noexcept {
|
||||
if (!dis_flag_.load(std::memory_order_acquire)) {
|
||||
cc_.fetch_sub(1, std::memory_order_relaxed);
|
||||
dis_flag_.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
void clear_dis_flag(std::memory_order order = std::memory_order_release) noexcept {
|
||||
dis_flag_.store(false, order);
|
||||
}
|
||||
|
||||
bool dis_flag(std::memory_order order = std::memory_order_acquire) const noexcept {
|
||||
return dis_flag_.load(order);
|
||||
}
|
||||
|
||||
std::size_t conn_count(std::memory_order order = std::memory_order_acquire) const noexcept {
|
||||
return cc_.load(order);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace circ
|
||||
} // namespace ipc
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <new>
|
||||
|
||||
#include "rw_lock.h"
|
||||
|
||||
#include "platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace circ {
|
||||
|
||||
using u1_t = ipc::uint_t<8>;
|
||||
using u2_t = ipc::uint_t<32>;
|
||||
|
||||
constexpr u1_t index_of(u2_t c) noexcept {
|
||||
return static_cast<u1_t>(c);
|
||||
}
|
||||
|
||||
class conn_head {
|
||||
std::atomic<u2_t> cc_ { 0 }; // connection counter
|
||||
|
||||
ipc::spin_lock lc_;
|
||||
std::atomic<bool> constructed_;
|
||||
|
||||
std::atomic<bool> dis_flag_;
|
||||
|
||||
public:
|
||||
void init() {
|
||||
/* DCLP */
|
||||
if (!constructed_.load(std::memory_order_acquire)) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lc_);
|
||||
if (!constructed_.load(std::memory_order_relaxed)) {
|
||||
::new (this) conn_head;
|
||||
constructed_.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
conn_head() = default;
|
||||
conn_head(const conn_head&) = delete;
|
||||
conn_head& operator=(const conn_head&) = delete;
|
||||
|
||||
std::size_t connect() noexcept {
|
||||
return cc_.fetch_add(1, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
std::size_t disconnect() noexcept {
|
||||
return cc_.fetch_sub(1, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
void try_disconnect() noexcept {
|
||||
if (!dis_flag_.load(std::memory_order_acquire)) {
|
||||
cc_.fetch_sub(1, std::memory_order_relaxed);
|
||||
dis_flag_.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
void clear_dis_flag(std::memory_order order = std::memory_order_release) noexcept {
|
||||
dis_flag_.store(false, order);
|
||||
}
|
||||
|
||||
bool dis_flag(std::memory_order order = std::memory_order_acquire) const noexcept {
|
||||
return dis_flag_.load(order);
|
||||
}
|
||||
|
||||
std::size_t conn_count(std::memory_order order = std::memory_order_acquire) const noexcept {
|
||||
return cc_.load(order);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace circ
|
||||
} // namespace ipc
|
||||
|
||||
@ -1,29 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace ipc {
|
||||
|
||||
// concept helpers
|
||||
|
||||
template <bool Cond, typename R = void>
|
||||
using require = std::enable_if_t<Cond, R>;
|
||||
|
||||
#ifdef IPC_CONCEPT_
|
||||
# error "IPC_CONCEPT_ has been defined."
|
||||
#endif
|
||||
|
||||
#define IPC_CONCEPT_(NAME, WHAT) \
|
||||
template <typename T> \
|
||||
class NAME { \
|
||||
private: \
|
||||
template <typename Type> \
|
||||
static std::true_type check(decltype(std::declval<Type>().WHAT)*); \
|
||||
template <typename Type> \
|
||||
static std::false_type check(...); \
|
||||
public: \
|
||||
using type = decltype(check<T>(nullptr)); \
|
||||
constexpr static auto value = type::value; \
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace ipc {
|
||||
|
||||
// concept helpers
|
||||
|
||||
template <bool Cond, typename R = void>
|
||||
using require = std::enable_if_t<Cond, R>;
|
||||
|
||||
#ifdef IPC_CONCEPT_
|
||||
# error "IPC_CONCEPT_ has been defined."
|
||||
#endif
|
||||
|
||||
#define IPC_CONCEPT_(NAME, WHAT) \
|
||||
template <typename T> \
|
||||
class NAME { \
|
||||
private: \
|
||||
template <typename Type> \
|
||||
static std::true_type check(decltype(std::declval<Type>().WHAT)*); \
|
||||
template <typename Type> \
|
||||
static std::false_type check(...); \
|
||||
public: \
|
||||
using type = decltype(check<T>(nullptr)); \
|
||||
constexpr static auto value = type::value; \
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
970
src/ipc.cpp
970
src/ipc.cpp
@ -1,485 +1,485 @@
|
||||
#include "ipc.h"
|
||||
|
||||
#include <type_traits>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <atomic>
|
||||
#include <type_traits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "def.h"
|
||||
#include "shm.h"
|
||||
#include "tls_pointer.h"
|
||||
#include "pool_alloc.h"
|
||||
#include "queue.h"
|
||||
#include "policy.h"
|
||||
#include "rw_lock.h"
|
||||
#include "log.h"
|
||||
|
||||
#include "memory/resource.h"
|
||||
|
||||
#include "platform/detail.h"
|
||||
#include "platform/waiter_wrapper.h"
|
||||
|
||||
#include "circ/elem_array.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace ipc;
|
||||
using msg_id_t = std::size_t;
|
||||
|
||||
template <std::size_t DataSize, std::size_t AlignSize>
|
||||
struct msg_t;
|
||||
|
||||
template <std::size_t AlignSize>
|
||||
struct msg_t<0, AlignSize> {
|
||||
msg_id_t conn_;
|
||||
msg_id_t id_;
|
||||
int remain_;
|
||||
bool storage_;
|
||||
};
|
||||
|
||||
template <std::size_t DataSize, std::size_t AlignSize>
|
||||
struct msg_t : msg_t<0, AlignSize> {
|
||||
std::aligned_storage_t<DataSize, AlignSize> data_ {};
|
||||
|
||||
msg_t() = default;
|
||||
msg_t(msg_id_t c, msg_id_t i, int r, void const * d, std::size_t s)
|
||||
: msg_t<0, AlignSize> { c, i, r, (d == nullptr) || (s == 0) } {
|
||||
if (!this->storage_) {
|
||||
std::memcpy(&data_, d, s);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
buff_t make_cache(T& data, std::size_t size) {
|
||||
auto ptr = mem::alloc(size);
|
||||
std::memcpy(ptr, &data, (ipc::detail::min)(sizeof(data), size));
|
||||
return { ptr, size, mem::free };
|
||||
}
|
||||
|
||||
struct cache_t {
|
||||
std::size_t fill_;
|
||||
buff_t buff_;
|
||||
|
||||
cache_t(std::size_t f, buff_t&& b)
|
||||
: fill_(f), buff_(std::move(b))
|
||||
{}
|
||||
|
||||
void append(void const * data, std::size_t size) {
|
||||
if (fill_ >= buff_.size() || data == nullptr || size == 0) return;
|
||||
auto new_fill = (ipc::detail::min)(fill_ + size, buff_.size());
|
||||
std::memcpy(static_cast<byte_t*>(buff_.data()) + fill_, data, new_fill - fill_);
|
||||
fill_ = new_fill;
|
||||
}
|
||||
};
|
||||
|
||||
struct conn_info_head {
|
||||
using acc_t = std::atomic<msg_id_t>;
|
||||
|
||||
static auto cc_acc() {
|
||||
static shm::handle acc_h("__CA_CONN__", sizeof(acc_t));
|
||||
return static_cast<acc_t*>(acc_h.get());
|
||||
}
|
||||
|
||||
ipc::string name_;
|
||||
msg_id_t cc_id_; // connection-info id
|
||||
waiter cc_waiter_, wt_waiter_, rd_waiter_;
|
||||
shm::handle acc_h_;
|
||||
|
||||
/*
|
||||
* <Remarks> thread_local may have some bugs.
|
||||
*
|
||||
* <Reference>
|
||||
* - https://sourceforge.net/p/mingw-w64/bugs/727/
|
||||
* - https://sourceforge.net/p/mingw-w64/bugs/527/
|
||||
* - https://github.com/Alexpux/MINGW-packages/issues/2519
|
||||
* - https://github.com/ChaiScript/ChaiScript/issues/402
|
||||
* - https://developercommunity.visualstudio.com/content/problem/124121/thread-local-variables-fail-to-be-initialized-when.html
|
||||
* - https://software.intel.com/en-us/forums/intel-c-compiler/topic/684827
|
||||
*/
|
||||
tls::pointer<ipc::unordered_map<msg_id_t, cache_t>> recv_cache_;
|
||||
|
||||
struct simple_push {
|
||||
|
||||
template <std::size_t, std::size_t>
|
||||
using elem_t = shm::id_t;
|
||||
|
||||
circ::u2_t wt_; // write index
|
||||
|
||||
constexpr circ::u2_t cursor() const noexcept {
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool push(W* /*wrapper*/, F&& f, E* elems) {
|
||||
std::forward<F>(f)(&(elems[circ::index_of(wt_)]));
|
||||
++ wt_;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
circ::elem_array<simple_push, sizeof(shm::id_t)> msg_datas_;
|
||||
|
||||
conn_info_head(char const * name)
|
||||
: name_ (name)
|
||||
, cc_id_ ((cc_acc() == nullptr) ? 0 : cc_acc()->fetch_add(1, std::memory_order_relaxed))
|
||||
, cc_waiter_(("__CC_CONN__" + name_).c_str())
|
||||
, wt_waiter_(("__WT_CONN__" + name_).c_str())
|
||||
, rd_waiter_(("__RD_CONN__" + name_).c_str())
|
||||
, acc_h_ (("__AC_CONN__" + name_).c_str(), sizeof(acc_t)) {
|
||||
}
|
||||
|
||||
auto acc() {
|
||||
return static_cast<acc_t*>(acc_h_.get());
|
||||
}
|
||||
|
||||
auto& recv_cache() {
|
||||
return *recv_cache_.create();
|
||||
}
|
||||
|
||||
shm::id_t apply_storage(msg_id_t msg_id, std::size_t size) {
|
||||
return shm::acquire(
|
||||
("__ST_CONN__" + ipc::to_string(cc_id_) +
|
||||
"__" + ipc::to_string(msg_id)).c_str(), size, shm::create);
|
||||
}
|
||||
|
||||
static shm::id_t acquire_storage(msg_id_t cc_id, msg_id_t msg_id) {
|
||||
return shm::acquire(
|
||||
("__ST_CONN__" + ipc::to_string(cc_id) +
|
||||
"__" + ipc::to_string(msg_id)).c_str(), 0, shm::open);
|
||||
}
|
||||
|
||||
void store(shm::id_t dat) {
|
||||
msg_datas_.push([dat](shm::id_t * id) {
|
||||
(*id) = dat;
|
||||
});
|
||||
}
|
||||
|
||||
void clear_store() {
|
||||
msg_datas_.push([](shm::id_t * id) {
|
||||
if (*id == nullptr) return;
|
||||
shm::remove(*id);
|
||||
(*id) = nullptr;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
template <typename W, typename F>
|
||||
bool wait_for(W& waiter, F&& pred, std::size_t tm) {
|
||||
if (tm == 0) return !pred();
|
||||
for (unsigned k = 0; pred();) {
|
||||
bool loop = true, ret = true;
|
||||
ipc::sleep(k, [&k, &loop, &ret, &waiter, &pred, tm] {
|
||||
ret = waiter.wait_if([&loop, &pred] {
|
||||
return loop = pred();
|
||||
}, tm);
|
||||
k = 0;
|
||||
return true;
|
||||
});
|
||||
if (!ret ) return false; // timeout or fail
|
||||
if (!loop) break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Policy,
|
||||
std::size_t DataSize = data_length,
|
||||
std::size_t AlignSize = (ipc::detail::min)(DataSize, alignof(std::max_align_t))>
|
||||
struct queue_generator {
|
||||
|
||||
using queue_t = ipc::queue<msg_t<DataSize, AlignSize>, Policy>;
|
||||
|
||||
struct conn_info_t : conn_info_head {
|
||||
queue_t que_;
|
||||
|
||||
conn_info_t(char const * name)
|
||||
: conn_info_head(name)
|
||||
, que_(("__QU_CONN__" +
|
||||
ipc::to_string(DataSize) + "__" +
|
||||
ipc::to_string(AlignSize) + "__" + name).c_str()) {
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
template <typename Policy>
|
||||
struct detail_impl {
|
||||
|
||||
using queue_t = typename queue_generator<Policy>::queue_t;
|
||||
using conn_info_t = typename queue_generator<Policy>::conn_info_t;
|
||||
|
||||
constexpr static conn_info_t* info_of(ipc::handle_t h) {
|
||||
return static_cast<conn_info_t*>(h);
|
||||
}
|
||||
|
||||
constexpr static queue_t* queue_of(ipc::handle_t h) {
|
||||
return (info_of(h) == nullptr) ? nullptr : &(info_of(h)->que_);
|
||||
}
|
||||
|
||||
/* API implementations */
|
||||
|
||||
static ipc::handle_t connect(char const * name, bool start) {
|
||||
auto h = mem::alloc<conn_info_t>(name);
|
||||
auto que = queue_of(h);
|
||||
if (que == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
if (start) {
|
||||
if (que->connect()) { // wouldn't connect twice
|
||||
info_of(h)->cc_waiter_.broadcast();
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
static void disconnect(ipc::handle_t h) {
|
||||
auto que = queue_of(h);
|
||||
if (que == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (que->disconnect()) {
|
||||
info_of(h)->cc_waiter_.broadcast();
|
||||
}
|
||||
mem::free(info_of(h));
|
||||
}
|
||||
|
||||
static std::size_t recv_count(ipc::handle_t h) {
|
||||
auto que = queue_of(h);
|
||||
if (que == nullptr) {
|
||||
return invalid_value;
|
||||
}
|
||||
return que->conn_count();
|
||||
}
|
||||
|
||||
static bool wait_for_recv(ipc::handle_t h, std::size_t r_count, std::size_t tm) {
|
||||
auto que = queue_of(h);
|
||||
if (que == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return wait_for(info_of(h)->cc_waiter_, [que, r_count] {
|
||||
return que->conn_count() < r_count;
|
||||
}, tm);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
static bool send(F&& gen_push, ipc::handle_t h, void const * data, std::size_t size) {
|
||||
if (data == nullptr || size == 0) {
|
||||
ipc::error("fail: send(%p, %zd)\n", data, size);
|
||||
return false;
|
||||
}
|
||||
auto que = queue_of(h);
|
||||
if (que == nullptr) {
|
||||
ipc::error("fail: send, queue_of(h) == nullptr\n");
|
||||
return false;
|
||||
}
|
||||
// calc a new message id
|
||||
auto acc = info_of(h)->acc();
|
||||
if (acc == nullptr) {
|
||||
ipc::error("fail: send, info_of(h)->acc() == nullptr\n");
|
||||
return false;
|
||||
}
|
||||
auto msg_id = acc->fetch_add(1, std::memory_order_relaxed);
|
||||
auto try_push = std::forward<F>(gen_push)(info_of(h), que, msg_id);
|
||||
if (size > small_msg_limit) {
|
||||
auto dat = info_of(h)->apply_storage(msg_id, size);
|
||||
void * buf = shm::get_mem(dat, nullptr);
|
||||
if (buf != nullptr) {
|
||||
std::memcpy(buf, data, size);
|
||||
info_of(h)->store(dat);
|
||||
return try_push(static_cast<int>(size) - static_cast<int>(data_length), nullptr, 0);
|
||||
}
|
||||
// try using message fragment
|
||||
ipc::log("fail: shm::handle for big message. msg_id: %zd, size: %zd\n", msg_id, size);
|
||||
}
|
||||
// push message fragment
|
||||
int offset = 0;
|
||||
for (int i = 0; i < static_cast<int>(size / data_length); ++i, offset += data_length) {
|
||||
if (!try_push(static_cast<int>(size) - offset - static_cast<int>(data_length),
|
||||
static_cast<byte_t const *>(data) + offset, data_length)) {
|
||||
return false;
|
||||
}
|
||||
info_of(h)->clear_store();
|
||||
}
|
||||
// if remain > 0, this is the last message fragment
|
||||
int remain = static_cast<int>(size) - offset;
|
||||
if (remain > 0) {
|
||||
if (!try_push(remain - static_cast<int>(data_length),
|
||||
static_cast<byte_t const *>(data) + offset, static_cast<std::size_t>(remain))) {
|
||||
return false;
|
||||
}
|
||||
info_of(h)->clear_store();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool send(ipc::handle_t h, void const * data, std::size_t size) {
|
||||
return send([](auto info, auto que, auto msg_id) {
|
||||
return [info, que, msg_id](int remain, void const * data, std::size_t size) {
|
||||
if (!wait_for(info->wt_waiter_, [&] {
|
||||
return !que->push(info->cc_id_, msg_id, remain, data, size);
|
||||
}, que->dis_flag() ? 0 : static_cast<std::size_t>(default_timeut))) {
|
||||
ipc::log("force_push: msg_id = %zd, remain = %d, size = %zd\n", msg_id, remain, size);
|
||||
if (!que->force_push(info->cc_id_, msg_id, remain, data, size)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
info->rd_waiter_.broadcast();
|
||||
return true;
|
||||
};
|
||||
}, h, data, size);
|
||||
}
|
||||
|
||||
static bool try_send(ipc::handle_t h, void const * data, std::size_t size) {
|
||||
return send([](auto info, auto que, auto msg_id) {
|
||||
return [info, que, msg_id](int remain, void const * data, std::size_t size) {
|
||||
if (!wait_for(info->wt_waiter_, [&] {
|
||||
return !que->push(info->cc_id_, msg_id, remain, data, size);
|
||||
}, 0)) {
|
||||
return false;
|
||||
}
|
||||
info->rd_waiter_.broadcast();
|
||||
return true;
|
||||
};
|
||||
}, h, data, size);
|
||||
}
|
||||
|
||||
static buff_t recv(ipc::handle_t h, std::size_t tm) {
|
||||
auto que = queue_of(h);
|
||||
if (que == nullptr) {
|
||||
ipc::error("fail: recv, queue_of(h) == nullptr\n");
|
||||
return {};
|
||||
}
|
||||
if (que->connect()) { // wouldn't connect twice
|
||||
info_of(h)->cc_waiter_.broadcast();
|
||||
}
|
||||
auto& rc = info_of(h)->recv_cache();
|
||||
while (1) {
|
||||
// pop a new message
|
||||
typename queue_t::value_t msg;
|
||||
if (!wait_for(info_of(h)->rd_waiter_, [que, &msg] { return !que->pop(msg); }, tm)) {
|
||||
return {};
|
||||
}
|
||||
info_of(h)->wt_waiter_.broadcast();
|
||||
if ((info_of(h)->acc() != nullptr) && (msg.conn_ == info_of(h)->cc_id_)) {
|
||||
continue; // ignore message to self
|
||||
}
|
||||
// msg.remain_ may minus & abs(msg.remain_) < data_length
|
||||
auto remain = static_cast<std::size_t>(static_cast<int>(data_length) + msg.remain_);
|
||||
// find cache with msg.id_
|
||||
auto cac_it = rc.find(msg.id_);
|
||||
if (cac_it == rc.end()) {
|
||||
if (remain <= data_length) {
|
||||
return make_cache(msg.data_, remain);
|
||||
}
|
||||
if (msg.storage_) {
|
||||
auto dat = info_of(h)->acquire_storage(msg.conn_, msg.id_);
|
||||
std::size_t dat_sz = 0;
|
||||
void * buf = shm::get_mem(dat, &dat_sz);
|
||||
if (buf != nullptr && remain <= dat_sz) {
|
||||
return buff_t { buf, remain, [](void * dat, std::size_t) {
|
||||
shm::release(dat);
|
||||
}, dat };
|
||||
}
|
||||
else ipc::log("fail: shm::handle for big message. msg_id: %zd, size: %zd, shm.size: %zd\n",
|
||||
msg.id_, remain, dat_sz);
|
||||
}
|
||||
// gc
|
||||
if (rc.size() > 1024) {
|
||||
std::vector<msg_id_t> need_del;
|
||||
for (auto const & pair : rc) {
|
||||
auto cmp = std::minmax(msg.id_, pair.first);
|
||||
if (cmp.second - cmp.first > 8192) {
|
||||
need_del.push_back(pair.first);
|
||||
}
|
||||
}
|
||||
for (auto id : need_del) rc.erase(id);
|
||||
}
|
||||
// cache the first message fragment
|
||||
rc.emplace(msg.id_, cache_t { data_length, make_cache(msg.data_, remain) });
|
||||
}
|
||||
// has cached before this message
|
||||
else {
|
||||
auto& cac = cac_it->second;
|
||||
// this is the last message fragment
|
||||
if (msg.remain_ <= 0) {
|
||||
cac.append(&(msg.data_), remain);
|
||||
// finish this message, erase it from cache
|
||||
auto buff = std::move(cac.buff_);
|
||||
rc.erase(cac_it);
|
||||
return buff;
|
||||
}
|
||||
// there are remain datas after this message
|
||||
cac.append(&(msg.data_), data_length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static buff_t try_recv(ipc::handle_t h) {
|
||||
return recv(h, 0);
|
||||
}
|
||||
|
||||
}; // detail_impl<Policy>
|
||||
|
||||
template <typename Flag>
|
||||
using policy_t = policy::choose<circ::elem_array, Flag>;
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
namespace ipc {
|
||||
|
||||
template <typename Flag>
|
||||
ipc::handle_t chan_impl<Flag>::connect(char const * name, unsigned mode) {
|
||||
return detail_impl<policy_t<Flag>>::connect(name, mode & receiver);
|
||||
}
|
||||
|
||||
template <typename Flag>
|
||||
void chan_impl<Flag>::disconnect(ipc::handle_t h) {
|
||||
detail_impl<policy_t<Flag>>::disconnect(h);
|
||||
}
|
||||
|
||||
template <typename Flag>
|
||||
char const * chan_impl<Flag>::name(ipc::handle_t h) {
|
||||
auto info = detail_impl<policy_t<Flag>>::info_of(h);
|
||||
return (info == nullptr) ? nullptr : info->name_.c_str();
|
||||
}
|
||||
|
||||
template <typename Flag>
|
||||
std::size_t chan_impl<Flag>::recv_count(ipc::handle_t h) {
|
||||
return detail_impl<policy_t<Flag>>::recv_count(h);
|
||||
}
|
||||
|
||||
template <typename Flag>
|
||||
bool chan_impl<Flag>::wait_for_recv(ipc::handle_t h, std::size_t r_count, std::size_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) {
|
||||
return detail_impl<policy_t<Flag>>::send(h, data, size);
|
||||
}
|
||||
|
||||
template <typename Flag>
|
||||
buff_t chan_impl<Flag>::recv(ipc::handle_t h, std::size_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) {
|
||||
return detail_impl<policy_t<Flag>>::try_send(h, data, size);
|
||||
}
|
||||
|
||||
template <typename Flag>
|
||||
buff_t chan_impl<Flag>::try_recv(ipc::handle_t h) {
|
||||
return detail_impl<policy_t<Flag>>::try_recv(h);
|
||||
}
|
||||
|
||||
template struct chan_impl<ipc::wr<relat::single, relat::single, trans::unicast >>;
|
||||
template struct chan_impl<ipc::wr<relat::single, relat::multi , trans::unicast >>;
|
||||
template struct chan_impl<ipc::wr<relat::multi , relat::multi , trans::unicast >>;
|
||||
template struct chan_impl<ipc::wr<relat::single, relat::multi , trans::broadcast>>;
|
||||
template struct chan_impl<ipc::wr<relat::multi , relat::multi , trans::broadcast>>;
|
||||
|
||||
} // namespace ipc
|
||||
#include "ipc.h"
|
||||
|
||||
#include <type_traits>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <atomic>
|
||||
#include <type_traits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "def.h"
|
||||
#include "shm.h"
|
||||
#include "tls_pointer.h"
|
||||
#include "pool_alloc.h"
|
||||
#include "queue.h"
|
||||
#include "policy.h"
|
||||
#include "rw_lock.h"
|
||||
#include "log.h"
|
||||
|
||||
#include "memory/resource.h"
|
||||
|
||||
#include "platform/detail.h"
|
||||
#include "platform/waiter_wrapper.h"
|
||||
|
||||
#include "circ/elem_array.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace ipc;
|
||||
using msg_id_t = std::uint32_t;
|
||||
|
||||
template <std::size_t DataSize, std::size_t AlignSize>
|
||||
struct msg_t;
|
||||
|
||||
template <std::size_t AlignSize>
|
||||
struct msg_t<0, AlignSize> {
|
||||
msg_id_t conn_;
|
||||
msg_id_t id_;
|
||||
std::int32_t remain_;
|
||||
bool storage_;
|
||||
};
|
||||
|
||||
template <std::size_t DataSize, std::size_t AlignSize>
|
||||
struct msg_t : msg_t<0, AlignSize> {
|
||||
std::aligned_storage_t<DataSize, AlignSize> data_ {};
|
||||
|
||||
msg_t() = default;
|
||||
msg_t(msg_id_t c, msg_id_t i, std::int32_t r, void const * d, std::size_t s)
|
||||
: msg_t<0, AlignSize> { c, i, r, (d == nullptr) || (s == 0) } {
|
||||
if (!this->storage_) {
|
||||
std::memcpy(&data_, d, s);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
buff_t make_cache(T& data, std::size_t size) {
|
||||
auto ptr = mem::alloc(size);
|
||||
std::memcpy(ptr, &data, (ipc::detail::min)(sizeof(data), size));
|
||||
return { ptr, size, mem::free };
|
||||
}
|
||||
|
||||
struct cache_t {
|
||||
std::size_t fill_;
|
||||
buff_t buff_;
|
||||
|
||||
cache_t(std::size_t f, buff_t&& b)
|
||||
: fill_(f), buff_(std::move(b))
|
||||
{}
|
||||
|
||||
void append(void const * data, std::size_t size) {
|
||||
if (fill_ >= buff_.size() || data == nullptr || size == 0) return;
|
||||
auto new_fill = (ipc::detail::min)(fill_ + size, buff_.size());
|
||||
std::memcpy(static_cast<byte_t*>(buff_.data()) + fill_, data, new_fill - fill_);
|
||||
fill_ = new_fill;
|
||||
}
|
||||
};
|
||||
|
||||
struct conn_info_head {
|
||||
using acc_t = std::atomic<msg_id_t>;
|
||||
|
||||
static auto cc_acc() {
|
||||
static shm::handle acc_h("__CA_CONN__", sizeof(acc_t));
|
||||
return static_cast<acc_t*>(acc_h.get());
|
||||
}
|
||||
|
||||
ipc::string name_;
|
||||
msg_id_t cc_id_; // connection-info id
|
||||
waiter cc_waiter_, wt_waiter_, rd_waiter_;
|
||||
shm::handle acc_h_;
|
||||
|
||||
/*
|
||||
* <Remarks> thread_local may have some bugs.
|
||||
*
|
||||
* <Reference>
|
||||
* - https://sourceforge.net/p/mingw-w64/bugs/727/
|
||||
* - https://sourceforge.net/p/mingw-w64/bugs/527/
|
||||
* - https://github.com/Alexpux/MINGW-packages/issues/2519
|
||||
* - https://github.com/ChaiScript/ChaiScript/issues/402
|
||||
* - https://developercommunity.visualstudio.com/content/problem/124121/thread-local-variables-fail-to-be-initialized-when.html
|
||||
* - https://software.intel.com/en-us/forums/intel-c-compiler/topic/684827
|
||||
*/
|
||||
tls::pointer<ipc::unordered_map<msg_id_t, cache_t>> recv_cache_;
|
||||
|
||||
struct simple_push {
|
||||
|
||||
template <std::size_t, std::size_t>
|
||||
using elem_t = shm::id_t;
|
||||
|
||||
circ::u2_t wt_; // write index
|
||||
|
||||
constexpr circ::u2_t cursor() const noexcept {
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool push(W* /*wrapper*/, F&& f, E* elems) {
|
||||
std::forward<F>(f)(&(elems[circ::index_of(wt_)]));
|
||||
++ wt_;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
circ::elem_array<simple_push, sizeof(shm::id_t)> msg_datas_;
|
||||
|
||||
conn_info_head(char const * name)
|
||||
: name_ (name)
|
||||
, cc_id_ ((cc_acc() == nullptr) ? 0 : cc_acc()->fetch_add(1, std::memory_order_relaxed))
|
||||
, cc_waiter_(("__CC_CONN__" + name_).c_str())
|
||||
, wt_waiter_(("__WT_CONN__" + name_).c_str())
|
||||
, rd_waiter_(("__RD_CONN__" + name_).c_str())
|
||||
, acc_h_ (("__AC_CONN__" + name_).c_str(), sizeof(acc_t)) {
|
||||
}
|
||||
|
||||
auto acc() {
|
||||
return static_cast<acc_t*>(acc_h_.get());
|
||||
}
|
||||
|
||||
auto& recv_cache() {
|
||||
return *recv_cache_.create();
|
||||
}
|
||||
|
||||
shm::id_t apply_storage(msg_id_t msg_id, std::size_t size) {
|
||||
return shm::acquire(
|
||||
("__ST_CONN__" + ipc::to_string(cc_id_) +
|
||||
"__" + ipc::to_string(msg_id)).c_str(), size, shm::create);
|
||||
}
|
||||
|
||||
static shm::id_t acquire_storage(msg_id_t cc_id, msg_id_t msg_id) {
|
||||
return shm::acquire(
|
||||
("__ST_CONN__" + ipc::to_string(cc_id) +
|
||||
"__" + ipc::to_string(msg_id)).c_str(), 0, shm::open);
|
||||
}
|
||||
|
||||
void store(shm::id_t dat) {
|
||||
msg_datas_.push([dat](shm::id_t * id) {
|
||||
(*id) = dat;
|
||||
});
|
||||
}
|
||||
|
||||
void clear_store() {
|
||||
msg_datas_.push([](shm::id_t * id) {
|
||||
if (*id == nullptr) return;
|
||||
shm::remove(*id);
|
||||
(*id) = nullptr;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
template <typename W, typename F>
|
||||
bool wait_for(W& waiter, F&& pred, std::size_t tm) {
|
||||
if (tm == 0) return !pred();
|
||||
for (unsigned k = 0; pred();) {
|
||||
bool loop = true, ret = true;
|
||||
ipc::sleep(k, [&k, &loop, &ret, &waiter, &pred, tm] {
|
||||
ret = waiter.wait_if([&loop, &pred] {
|
||||
return loop = pred();
|
||||
}, tm);
|
||||
k = 0;
|
||||
return true;
|
||||
});
|
||||
if (!ret ) return false; // timeout or fail
|
||||
if (!loop) break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Policy,
|
||||
std::size_t DataSize = data_length,
|
||||
std::size_t AlignSize = (ipc::detail::min)(DataSize, alignof(std::max_align_t))>
|
||||
struct queue_generator {
|
||||
|
||||
using queue_t = ipc::queue<msg_t<DataSize, AlignSize>, Policy>;
|
||||
|
||||
struct conn_info_t : conn_info_head {
|
||||
queue_t que_;
|
||||
|
||||
conn_info_t(char const * name)
|
||||
: conn_info_head(name)
|
||||
, que_(("__QU_CONN__" +
|
||||
ipc::to_string(DataSize) + "__" +
|
||||
ipc::to_string(AlignSize) + "__" + name).c_str()) {
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
template <typename Policy>
|
||||
struct detail_impl {
|
||||
|
||||
using queue_t = typename queue_generator<Policy>::queue_t;
|
||||
using conn_info_t = typename queue_generator<Policy>::conn_info_t;
|
||||
|
||||
constexpr static conn_info_t* info_of(ipc::handle_t h) {
|
||||
return static_cast<conn_info_t*>(h);
|
||||
}
|
||||
|
||||
constexpr static queue_t* queue_of(ipc::handle_t h) {
|
||||
return (info_of(h) == nullptr) ? nullptr : &(info_of(h)->que_);
|
||||
}
|
||||
|
||||
/* API implementations */
|
||||
|
||||
static ipc::handle_t connect(char const * name, bool start) {
|
||||
auto h = mem::alloc<conn_info_t>(name);
|
||||
auto que = queue_of(h);
|
||||
if (que == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
if (start) {
|
||||
if (que->connect()) { // wouldn't connect twice
|
||||
info_of(h)->cc_waiter_.broadcast();
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
static void disconnect(ipc::handle_t h) {
|
||||
auto que = queue_of(h);
|
||||
if (que == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (que->disconnect()) {
|
||||
info_of(h)->cc_waiter_.broadcast();
|
||||
}
|
||||
mem::free(info_of(h));
|
||||
}
|
||||
|
||||
static std::size_t recv_count(ipc::handle_t h) {
|
||||
auto que = queue_of(h);
|
||||
if (que == nullptr) {
|
||||
return invalid_value;
|
||||
}
|
||||
return que->conn_count();
|
||||
}
|
||||
|
||||
static bool wait_for_recv(ipc::handle_t h, std::size_t r_count, std::size_t tm) {
|
||||
auto que = queue_of(h);
|
||||
if (que == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return wait_for(info_of(h)->cc_waiter_, [que, r_count] {
|
||||
return que->conn_count() < r_count;
|
||||
}, tm);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
static bool send(F&& gen_push, ipc::handle_t h, void const * data, std::size_t size) {
|
||||
if (data == nullptr || size == 0) {
|
||||
ipc::error("fail: send(%p, %zd)\n", data, size);
|
||||
return false;
|
||||
}
|
||||
auto que = queue_of(h);
|
||||
if (que == nullptr) {
|
||||
ipc::error("fail: send, queue_of(h) == nullptr\n");
|
||||
return false;
|
||||
}
|
||||
// calc a new message id
|
||||
auto acc = info_of(h)->acc();
|
||||
if (acc == nullptr) {
|
||||
ipc::error("fail: send, info_of(h)->acc() == nullptr\n");
|
||||
return false;
|
||||
}
|
||||
auto msg_id = acc->fetch_add(1, std::memory_order_relaxed);
|
||||
auto try_push = std::forward<F>(gen_push)(info_of(h), que, msg_id);
|
||||
if (size > small_msg_limit) {
|
||||
auto dat = info_of(h)->apply_storage(msg_id, size);
|
||||
void * buf = shm::get_mem(dat, nullptr);
|
||||
if (buf != nullptr) {
|
||||
std::memcpy(buf, data, size);
|
||||
info_of(h)->store(dat);
|
||||
return try_push(static_cast<int>(size) - static_cast<int>(data_length), nullptr, 0);
|
||||
}
|
||||
// try using message fragment
|
||||
ipc::log("fail: shm::handle for big message. msg_id: %zd, size: %zd\n", msg_id, size);
|
||||
}
|
||||
// push message fragment
|
||||
int offset = 0;
|
||||
for (int i = 0; i < static_cast<int>(size / data_length); ++i, offset += data_length) {
|
||||
if (!try_push(static_cast<int>(size) - offset - static_cast<int>(data_length),
|
||||
static_cast<byte_t const *>(data) + offset, data_length)) {
|
||||
return false;
|
||||
}
|
||||
info_of(h)->clear_store();
|
||||
}
|
||||
// if remain > 0, this is the last message fragment
|
||||
int remain = static_cast<int>(size) - offset;
|
||||
if (remain > 0) {
|
||||
if (!try_push(remain - static_cast<int>(data_length),
|
||||
static_cast<byte_t const *>(data) + offset, static_cast<std::size_t>(remain))) {
|
||||
return false;
|
||||
}
|
||||
info_of(h)->clear_store();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool send(ipc::handle_t h, void const * data, std::size_t size) {
|
||||
return send([](auto info, auto que, auto msg_id) {
|
||||
return [info, que, msg_id](int remain, void const * data, std::size_t size) {
|
||||
if (!wait_for(info->wt_waiter_, [&] {
|
||||
return !que->push(info->cc_id_, msg_id, remain, data, size);
|
||||
}, que->dis_flag() ? 0 : static_cast<std::size_t>(default_timeut))) {
|
||||
ipc::log("force_push: msg_id = %zd, remain = %d, size = %zd\n", msg_id, remain, size);
|
||||
if (!que->force_push(info->cc_id_, msg_id, remain, data, size)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
info->rd_waiter_.broadcast();
|
||||
return true;
|
||||
};
|
||||
}, h, data, size);
|
||||
}
|
||||
|
||||
static bool try_send(ipc::handle_t h, void const * data, std::size_t size) {
|
||||
return send([](auto info, auto que, auto msg_id) {
|
||||
return [info, que, msg_id](int remain, void const * data, std::size_t size) {
|
||||
if (!wait_for(info->wt_waiter_, [&] {
|
||||
return !que->push(info->cc_id_, msg_id, remain, data, size);
|
||||
}, 0)) {
|
||||
return false;
|
||||
}
|
||||
info->rd_waiter_.broadcast();
|
||||
return true;
|
||||
};
|
||||
}, h, data, size);
|
||||
}
|
||||
|
||||
static buff_t recv(ipc::handle_t h, std::size_t tm) {
|
||||
auto que = queue_of(h);
|
||||
if (que == nullptr) {
|
||||
ipc::error("fail: recv, queue_of(h) == nullptr\n");
|
||||
return {};
|
||||
}
|
||||
if (que->connect()) { // wouldn't connect twice
|
||||
info_of(h)->cc_waiter_.broadcast();
|
||||
}
|
||||
auto& rc = info_of(h)->recv_cache();
|
||||
while (1) {
|
||||
// pop a new message
|
||||
typename queue_t::value_t msg;
|
||||
if (!wait_for(info_of(h)->rd_waiter_, [que, &msg] { return !que->pop(msg); }, tm)) {
|
||||
return {};
|
||||
}
|
||||
info_of(h)->wt_waiter_.broadcast();
|
||||
if ((info_of(h)->acc() != nullptr) && (msg.conn_ == info_of(h)->cc_id_)) {
|
||||
continue; // ignore message to self
|
||||
}
|
||||
// msg.remain_ may minus & abs(msg.remain_) < data_length
|
||||
auto remain = static_cast<std::size_t>(static_cast<std::int32_t>(data_length) + msg.remain_);
|
||||
// find cache with msg.id_
|
||||
auto cac_it = rc.find(msg.id_);
|
||||
if (cac_it == rc.end()) {
|
||||
if (remain <= data_length) {
|
||||
return make_cache(msg.data_, remain);
|
||||
}
|
||||
if (msg.storage_) {
|
||||
auto dat = info_of(h)->acquire_storage(msg.conn_, msg.id_);
|
||||
std::size_t dat_sz = 0;
|
||||
void * buf = shm::get_mem(dat, &dat_sz);
|
||||
if (buf != nullptr && remain <= dat_sz) {
|
||||
return buff_t { buf, remain, [](void * dat, std::size_t) {
|
||||
shm::release(dat);
|
||||
}, dat };
|
||||
}
|
||||
else ipc::log("fail: shm::handle for big message. msg_id: %zd, size: %zd, shm.size: %zd\n",
|
||||
msg.id_, remain, dat_sz);
|
||||
}
|
||||
// gc
|
||||
if (rc.size() > 1024) {
|
||||
std::vector<msg_id_t> need_del;
|
||||
for (auto const & pair : rc) {
|
||||
auto cmp = std::minmax(msg.id_, pair.first);
|
||||
if (cmp.second - cmp.first > 8192) {
|
||||
need_del.push_back(pair.first);
|
||||
}
|
||||
}
|
||||
for (auto id : need_del) rc.erase(id);
|
||||
}
|
||||
// cache the first message fragment
|
||||
rc.emplace(msg.id_, cache_t { data_length, make_cache(msg.data_, remain) });
|
||||
}
|
||||
// has cached before this message
|
||||
else {
|
||||
auto& cac = cac_it->second;
|
||||
// this is the last message fragment
|
||||
if (msg.remain_ <= 0) {
|
||||
cac.append(&(msg.data_), remain);
|
||||
// finish this message, erase it from cache
|
||||
auto buff = std::move(cac.buff_);
|
||||
rc.erase(cac_it);
|
||||
return buff;
|
||||
}
|
||||
// there are remain datas after this message
|
||||
cac.append(&(msg.data_), data_length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static buff_t try_recv(ipc::handle_t h) {
|
||||
return recv(h, 0);
|
||||
}
|
||||
|
||||
}; // detail_impl<Policy>
|
||||
|
||||
template <typename Flag>
|
||||
using policy_t = policy::choose<circ::elem_array, Flag>;
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
namespace ipc {
|
||||
|
||||
template <typename Flag>
|
||||
ipc::handle_t chan_impl<Flag>::connect(char const * name, unsigned mode) {
|
||||
return detail_impl<policy_t<Flag>>::connect(name, mode & receiver);
|
||||
}
|
||||
|
||||
template <typename Flag>
|
||||
void chan_impl<Flag>::disconnect(ipc::handle_t h) {
|
||||
detail_impl<policy_t<Flag>>::disconnect(h);
|
||||
}
|
||||
|
||||
template <typename Flag>
|
||||
char const * chan_impl<Flag>::name(ipc::handle_t h) {
|
||||
auto info = detail_impl<policy_t<Flag>>::info_of(h);
|
||||
return (info == nullptr) ? nullptr : info->name_.c_str();
|
||||
}
|
||||
|
||||
template <typename Flag>
|
||||
std::size_t chan_impl<Flag>::recv_count(ipc::handle_t h) {
|
||||
return detail_impl<policy_t<Flag>>::recv_count(h);
|
||||
}
|
||||
|
||||
template <typename Flag>
|
||||
bool chan_impl<Flag>::wait_for_recv(ipc::handle_t h, std::size_t r_count, std::size_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) {
|
||||
return detail_impl<policy_t<Flag>>::send(h, data, size);
|
||||
}
|
||||
|
||||
template <typename Flag>
|
||||
buff_t chan_impl<Flag>::recv(ipc::handle_t h, std::size_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) {
|
||||
return detail_impl<policy_t<Flag>>::try_send(h, data, size);
|
||||
}
|
||||
|
||||
template <typename Flag>
|
||||
buff_t chan_impl<Flag>::try_recv(ipc::handle_t h) {
|
||||
return detail_impl<policy_t<Flag>>::try_recv(h);
|
||||
}
|
||||
|
||||
template struct chan_impl<ipc::wr<relat::single, relat::single, trans::unicast >>;
|
||||
template struct chan_impl<ipc::wr<relat::single, relat::multi , trans::unicast >>;
|
||||
template struct chan_impl<ipc::wr<relat::multi , relat::multi , trans::unicast >>;
|
||||
template struct chan_impl<ipc::wr<relat::single, relat::multi , trans::broadcast>>;
|
||||
template struct chan_impl<ipc::wr<relat::multi , relat::multi , trans::broadcast>>;
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
78
src/log.h
78
src/log.h
@ -1,39 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdio>
|
||||
#include <utility>
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
template <typename O>
|
||||
void print(O out, char const * fmt) {
|
||||
std::fprintf(out, "%s", fmt);
|
||||
}
|
||||
|
||||
template <typename O, typename P1, typename... P>
|
||||
void print(O out, char const * fmt, P1&& p1, P&&... params) {
|
||||
std::fprintf(out, fmt, std::forward<P1>(p1), std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
inline void log(char const * fmt) {
|
||||
ipc::detail::print(stdout, fmt);
|
||||
}
|
||||
|
||||
template <typename P1, typename... P>
|
||||
void log(char const * fmt, P1&& p1, P&&... params) {
|
||||
ipc::detail::print(stdout, fmt, std::forward<P1>(p1), std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
inline void error(char const * fmt) {
|
||||
ipc::detail::print(stderr, fmt);
|
||||
}
|
||||
|
||||
template <typename P1, typename... P>
|
||||
void error(char const * fmt, P1&& p1, P&&... params) {
|
||||
ipc::detail::print(stderr, fmt, std::forward<P1>(p1), std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
#pragma once
|
||||
|
||||
#include <cstdio>
|
||||
#include <utility>
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
template <typename O>
|
||||
void print(O out, char const * fmt) {
|
||||
std::fprintf(out, "%s", fmt);
|
||||
}
|
||||
|
||||
template <typename O, typename P1, typename... P>
|
||||
void print(O out, char const * fmt, P1&& p1, P&&... params) {
|
||||
std::fprintf(out, fmt, std::forward<P1>(p1), std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
inline void log(char const * fmt) {
|
||||
ipc::detail::print(stdout, fmt);
|
||||
}
|
||||
|
||||
template <typename P1, typename... P>
|
||||
void log(char const * fmt, P1&& p1, P&&... params) {
|
||||
ipc::detail::print(stdout, fmt, std::forward<P1>(p1), std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
inline void error(char const * fmt) {
|
||||
ipc::detail::print(stderr, fmt);
|
||||
}
|
||||
|
||||
template <typename P1, typename... P>
|
||||
void error(char const * fmt, P1&& p1, P&&... params) {
|
||||
ipc::detail::print(stderr, fmt, std::forward<P1>(p1), std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
@ -1,431 +1,432 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <cstdlib>
|
||||
#include <map>
|
||||
#include <iterator>
|
||||
|
||||
#include "def.h"
|
||||
#include "rw_lock.h"
|
||||
#include "concept.h"
|
||||
|
||||
#include "platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
class static_alloc {
|
||||
public:
|
||||
static void swap(static_alloc&) {}
|
||||
static void clear() {}
|
||||
|
||||
static void* alloc(std::size_t size) {
|
||||
return size ? std::malloc(size) : nullptr;
|
||||
}
|
||||
|
||||
static void free(void* p) {
|
||||
std::free(p);
|
||||
}
|
||||
|
||||
static void free(void* p, std::size_t /*size*/) {
|
||||
free(p);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Scope allocation -- The destructor will release all allocated blocks.
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
constexpr std::size_t aligned(std::size_t size, size_t alignment) noexcept {
|
||||
return ((size - 1) & ~(alignment - 1)) + alignment;
|
||||
}
|
||||
|
||||
IPC_CONCEPT_(has_take, take(Type{}));
|
||||
|
||||
class scope_alloc_base {
|
||||
protected:
|
||||
struct block_t {
|
||||
block_t * next_;
|
||||
std::size_t size_;
|
||||
} * head_ = nullptr, * tail_ = nullptr;
|
||||
|
||||
enum : std::size_t {
|
||||
aligned_block_size = aligned(sizeof(block_t), alignof(std::max_align_t))
|
||||
};
|
||||
|
||||
public:
|
||||
void swap(scope_alloc_base & rhs) {
|
||||
std::swap(head_, rhs.head_);
|
||||
std::swap(tail_, rhs.tail_);
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return head_ == nullptr;
|
||||
}
|
||||
|
||||
void take(scope_alloc_base && rhs) {
|
||||
if (rhs.empty()) return;
|
||||
if (empty()) swap(rhs);
|
||||
else {
|
||||
std::swap(tail_->next_, rhs.head_);
|
||||
// rhs.head_ should be nullptr here
|
||||
tail_ = rhs.tail_;
|
||||
rhs.tail_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void free(void* /*p*/) {}
|
||||
void free(void* /*p*/, std::size_t) {}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename AllocP = static_alloc>
|
||||
class scope_alloc : public detail::scope_alloc_base {
|
||||
public:
|
||||
using base_t = detail::scope_alloc_base;
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
private:
|
||||
alloc_policy alloc_;
|
||||
|
||||
void free_all() {
|
||||
while (!empty()) {
|
||||
auto curr = head_;
|
||||
head_ = head_->next_;
|
||||
alloc_.free(curr, curr->size_);
|
||||
}
|
||||
// now head_ is nullptr
|
||||
}
|
||||
|
||||
public:
|
||||
scope_alloc() = default;
|
||||
|
||||
scope_alloc(scope_alloc&& rhs) { swap(rhs); }
|
||||
scope_alloc& operator=(scope_alloc&& rhs) { swap(rhs); return (*this); }
|
||||
|
||||
~scope_alloc() { free_all(); }
|
||||
|
||||
template <typename A>
|
||||
void set_allocator(A && alc) {
|
||||
alloc_ = std::forward<A>(alc);
|
||||
}
|
||||
|
||||
void swap(scope_alloc& rhs) {
|
||||
alloc_.swap(rhs.alloc_);
|
||||
base_t::swap(rhs);
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto take(scope_alloc && rhs) -> ipc::require<detail::has_take<A>::value> {
|
||||
base_t::take(std::move(rhs));
|
||||
alloc_.take(std::move(rhs.alloc_));
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto take(scope_alloc && rhs) -> ipc::require<!detail::has_take<A>::value> {
|
||||
base_t::take(std::move(rhs));
|
||||
}
|
||||
|
||||
void clear() {
|
||||
free_all();
|
||||
tail_ = nullptr;
|
||||
alloc_.~alloc_policy();
|
||||
}
|
||||
|
||||
void* alloc(std::size_t size) {
|
||||
auto curr = static_cast<block_t*>(alloc_.alloc(size += aligned_block_size));
|
||||
curr->next_ = head_;
|
||||
curr->size_ = size;
|
||||
head_ = curr;
|
||||
if (tail_ == nullptr) {
|
||||
tail_ = curr;
|
||||
}
|
||||
return (reinterpret_cast<byte_t*>(curr) + aligned_block_size);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Fixed-size blocks allocation
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
class fixed_alloc_base {
|
||||
protected:
|
||||
std::size_t init_expand_;
|
||||
void * cursor_;
|
||||
|
||||
void init(std::size_t init_expand) {
|
||||
init_expand_ = init_expand;
|
||||
cursor_ = nullptr;
|
||||
}
|
||||
|
||||
static void** node_p(void* node) {
|
||||
return reinterpret_cast<void**>(node);
|
||||
}
|
||||
|
||||
static auto& next(void* node) {
|
||||
return *node_p(node);
|
||||
}
|
||||
|
||||
public:
|
||||
void swap(fixed_alloc_base& rhs) {
|
||||
std::swap(init_expand_, rhs.init_expand_);
|
||||
std::swap(cursor_ , rhs.cursor_);
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return cursor_ == nullptr;
|
||||
}
|
||||
|
||||
void take(fixed_alloc_base && rhs) {
|
||||
init_expand_ = (ipc::detail::max)(init_expand_, rhs.init_expand_);
|
||||
if (rhs.empty()) return;
|
||||
auto curr = cursor_;
|
||||
if (curr != nullptr) while (1) {
|
||||
auto next_cur = next(curr);
|
||||
if (next_cur == nullptr) {
|
||||
std::swap(next(curr), rhs.cursor_);
|
||||
return;
|
||||
}
|
||||
// next_cur != nullptr
|
||||
else curr = next_cur;
|
||||
}
|
||||
// curr == nullptr, means cursor_ == nullptr
|
||||
else std::swap(cursor_, rhs.cursor_);
|
||||
// rhs.cursor_ must be nullptr
|
||||
}
|
||||
|
||||
void free(void* p) {
|
||||
if (p == nullptr) return;
|
||||
next(p) = cursor_;
|
||||
cursor_ = p;
|
||||
}
|
||||
|
||||
void free(void* p, std::size_t) {
|
||||
free(p);
|
||||
}
|
||||
};
|
||||
|
||||
struct fixed_expand_policy {
|
||||
|
||||
enum : std::size_t {
|
||||
base_size = sizeof(void*) * 1024 / 2
|
||||
};
|
||||
|
||||
static std::size_t prev(std::size_t& e) {
|
||||
if ((e /= 2) == 0) e = 1;
|
||||
return e;
|
||||
}
|
||||
|
||||
static std::size_t next(std::size_t& e) {
|
||||
return e *= 2;
|
||||
}
|
||||
|
||||
template <std::size_t BlockSize>
|
||||
static std::size_t next(std::size_t & e) {
|
||||
return ipc::detail::max<std::size_t>(BlockSize, base_size) * next(e);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <std::size_t BlockSize,
|
||||
typename AllocP = scope_alloc<>,
|
||||
typename ExpandP = detail::fixed_expand_policy>
|
||||
class fixed_alloc : public detail::fixed_alloc_base {
|
||||
public:
|
||||
using base_t = detail::fixed_alloc_base;
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
enum : std::size_t {
|
||||
block_size = (ipc::detail::max)(BlockSize, sizeof(void*))
|
||||
};
|
||||
|
||||
private:
|
||||
alloc_policy alloc_;
|
||||
|
||||
void* try_expand() {
|
||||
if (empty()) {
|
||||
auto size = ExpandP::template next<block_size>(init_expand_);
|
||||
auto p = node_p(cursor_ = alloc_.alloc(size));
|
||||
for (std::size_t i = 0; i < (size / block_size) - 1; ++i)
|
||||
p = node_p((*p) = reinterpret_cast<byte_t*>(p) + block_size);
|
||||
(*p) = nullptr;
|
||||
}
|
||||
return cursor_;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit fixed_alloc(std::size_t init_expand = 1) {
|
||||
init(init_expand);
|
||||
}
|
||||
|
||||
fixed_alloc(fixed_alloc&& rhs) : fixed_alloc() { swap(rhs); }
|
||||
fixed_alloc& operator=(fixed_alloc&& rhs) { swap(rhs); return (*this); }
|
||||
|
||||
template <typename A>
|
||||
void set_allocator(A && alc) {
|
||||
alloc_ = std::forward<A>(alc);
|
||||
}
|
||||
|
||||
void swap(fixed_alloc& rhs) {
|
||||
alloc_.swap(rhs.alloc_);
|
||||
base_t::swap(rhs);
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto take(fixed_alloc && rhs) -> ipc::require<detail::has_take<A>::value> {
|
||||
base_t::take(std::move(rhs));
|
||||
alloc_.take(std::move(rhs.alloc_));
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto take(fixed_alloc && rhs) -> ipc::require<!detail::has_take<A>::value> {
|
||||
base_t::take(std::move(rhs));
|
||||
}
|
||||
|
||||
void clear() {
|
||||
ExpandP::prev(init_expand_);
|
||||
cursor_ = nullptr;
|
||||
alloc_.~alloc_policy();
|
||||
}
|
||||
|
||||
void* alloc() {
|
||||
void* p = try_expand();
|
||||
cursor_ = next(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
void* alloc(std::size_t) {
|
||||
return alloc();
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Variable-size blocks allocation (without alignment)
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
class variable_alloc_base {
|
||||
protected:
|
||||
struct head_t {
|
||||
std::size_t free_;
|
||||
} * head_ = nullptr;
|
||||
|
||||
std::map<std::size_t, head_t*, std::greater<std::size_t>> reserves_;
|
||||
|
||||
enum : std::size_t {
|
||||
aligned_head_size = aligned(sizeof(head_t), alignof(std::max_align_t))
|
||||
};
|
||||
|
||||
static byte_t * buffer(head_t* p) {
|
||||
return reinterpret_cast<byte_t*>(p) + aligned_head_size + p->free_;
|
||||
}
|
||||
|
||||
std::size_t remain() const noexcept {
|
||||
return (head_ == nullptr) ? 0 : head_->free_;
|
||||
}
|
||||
|
||||
public:
|
||||
void swap(variable_alloc_base& rhs) {
|
||||
std::swap(head_, rhs.head_);
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return remain() == 0;
|
||||
}
|
||||
|
||||
void take(variable_alloc_base && rhs) {
|
||||
if (rhs.remain() > remain()) {
|
||||
if (!empty()) {
|
||||
reserves_.emplace(head_->free_, head_);
|
||||
}
|
||||
head_ = rhs.head_;
|
||||
}
|
||||
else if (!rhs.empty()) {
|
||||
reserves_.emplace(rhs.head_->free_, rhs.head_);
|
||||
}
|
||||
rhs.head_ = nullptr;
|
||||
}
|
||||
|
||||
void free(void* /*p*/) {}
|
||||
void free(void* /*p*/, std::size_t) {}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <std::size_t ChunkSize = (sizeof(void*) * 1024), typename AllocP = scope_alloc<>>
|
||||
class variable_alloc : public detail::variable_alloc_base {
|
||||
public:
|
||||
using base_t = detail::variable_alloc_base;
|
||||
using head_t = base_t::head_t;
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
private:
|
||||
alloc_policy alloc_;
|
||||
|
||||
public:
|
||||
variable_alloc() = default;
|
||||
|
||||
variable_alloc(variable_alloc&& rhs) { swap(rhs); }
|
||||
variable_alloc& operator=(variable_alloc&& rhs) { swap(rhs); return (*this); }
|
||||
|
||||
template <typename A>
|
||||
void set_allocator(A && alc) {
|
||||
alloc_ = std::forward<A>(alc);
|
||||
}
|
||||
|
||||
void swap(variable_alloc& rhs) {
|
||||
alloc_.swap(rhs.alloc_);
|
||||
base_t::swap(rhs);
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto take(variable_alloc && rhs) -> ipc::require<detail::has_take<A>::value> {
|
||||
base_t::take(std::move(rhs));
|
||||
alloc_.take(std::move(rhs.alloc_));
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto take(variable_alloc && rhs) -> ipc::require<!detail::has_take<A>::value> {
|
||||
base_t::take(std::move(rhs));
|
||||
}
|
||||
|
||||
void clear() {
|
||||
alloc_.~alloc_policy();
|
||||
}
|
||||
|
||||
void* alloc(std::size_t size) {
|
||||
if (size >= (ChunkSize - aligned_head_size)) {
|
||||
return alloc_.alloc(size);
|
||||
}
|
||||
if (remain() < size) {
|
||||
auto it = reserves_.begin();
|
||||
if ((it == reserves_.end()) || (it->first < size)) {
|
||||
head_ = static_cast<head_t*>(alloc_.alloc(ChunkSize));
|
||||
head_->free_ = ChunkSize - aligned_head_size - size;
|
||||
}
|
||||
else {
|
||||
auto temp = it->second;
|
||||
temp->free_ -= size;
|
||||
reserves_.erase(it);
|
||||
if (remain() < temp->free_) {
|
||||
head_ = temp;
|
||||
}
|
||||
else return base_t::buffer(temp);
|
||||
}
|
||||
}
|
||||
// size shouldn't be 0 here, otherwise behavior is undefined
|
||||
else head_->free_ -= size;
|
||||
return base_t::buffer(head_);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <cstdlib>
|
||||
#include <map>
|
||||
#include <iterator>
|
||||
|
||||
#include "def.h"
|
||||
#include "rw_lock.h"
|
||||
#include "concept.h"
|
||||
|
||||
#include "platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
class static_alloc {
|
||||
public:
|
||||
static void swap(static_alloc&) {}
|
||||
static void clear() {}
|
||||
|
||||
static void* alloc(std::size_t size) {
|
||||
return size ? std::malloc(size) : nullptr;
|
||||
}
|
||||
|
||||
static void free(void* p) {
|
||||
std::free(p);
|
||||
}
|
||||
|
||||
static void free(void* p, std::size_t /*size*/) {
|
||||
free(p);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Scope allocation -- The destructor will release all allocated blocks.
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
constexpr std::size_t aligned(std::size_t size, size_t alignment) noexcept {
|
||||
return ((size - 1) & ~(alignment - 1)) + alignment;
|
||||
}
|
||||
|
||||
IPC_CONCEPT_(has_take, take(Type{}));
|
||||
|
||||
class scope_alloc_base {
|
||||
protected:
|
||||
struct block_t {
|
||||
block_t * next_;
|
||||
std::size_t size_;
|
||||
} * head_ = nullptr, * tail_ = nullptr;
|
||||
|
||||
enum : std::size_t {
|
||||
aligned_block_size = aligned(sizeof(block_t), alignof(std::max_align_t))
|
||||
};
|
||||
|
||||
public:
|
||||
void swap(scope_alloc_base & rhs) {
|
||||
std::swap(head_, rhs.head_);
|
||||
std::swap(tail_, rhs.tail_);
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return head_ == nullptr;
|
||||
}
|
||||
|
||||
void take(scope_alloc_base && rhs) {
|
||||
if (rhs.empty()) return;
|
||||
if (empty()) swap(rhs);
|
||||
else {
|
||||
std::swap(tail_->next_, rhs.head_);
|
||||
// rhs.head_ should be nullptr here
|
||||
tail_ = rhs.tail_;
|
||||
rhs.tail_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void free(void* /*p*/) {}
|
||||
void free(void* /*p*/, std::size_t) {}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename AllocP = static_alloc>
|
||||
class scope_alloc : public detail::scope_alloc_base {
|
||||
public:
|
||||
using base_t = detail::scope_alloc_base;
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
private:
|
||||
alloc_policy alloc_;
|
||||
|
||||
void free_all() {
|
||||
while (!empty()) {
|
||||
auto curr = head_;
|
||||
head_ = head_->next_;
|
||||
alloc_.free(curr, curr->size_);
|
||||
}
|
||||
// now head_ is nullptr
|
||||
}
|
||||
|
||||
public:
|
||||
scope_alloc() = default;
|
||||
|
||||
scope_alloc(scope_alloc&& rhs) { swap(rhs); }
|
||||
scope_alloc& operator=(scope_alloc rhs) { swap(rhs); return (*this); }
|
||||
|
||||
~scope_alloc() { free_all(); }
|
||||
|
||||
template <typename A>
|
||||
void set_allocator(A && alc) {
|
||||
alloc_ = std::forward<A>(alc);
|
||||
}
|
||||
|
||||
void swap(scope_alloc& rhs) {
|
||||
alloc_.swap(rhs.alloc_);
|
||||
base_t::swap(rhs);
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto take(scope_alloc && rhs) -> ipc::require<detail::has_take<A>::value> {
|
||||
base_t::take(std::move(rhs));
|
||||
alloc_.take(std::move(rhs.alloc_));
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto take(scope_alloc && rhs) -> ipc::require<!detail::has_take<A>::value> {
|
||||
base_t::take(std::move(rhs));
|
||||
}
|
||||
|
||||
void clear() {
|
||||
free_all();
|
||||
tail_ = nullptr;
|
||||
alloc_.~alloc_policy();
|
||||
}
|
||||
|
||||
void* alloc(std::size_t size) {
|
||||
auto curr = static_cast<block_t*>(alloc_.alloc(size += aligned_block_size));
|
||||
curr->next_ = head_;
|
||||
curr->size_ = size;
|
||||
head_ = curr;
|
||||
if (tail_ == nullptr) {
|
||||
tail_ = curr;
|
||||
}
|
||||
return (reinterpret_cast<byte_t*>(curr) + aligned_block_size);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Fixed-size blocks allocation
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
class fixed_alloc_base {
|
||||
protected:
|
||||
std::size_t init_expand_;
|
||||
void * cursor_;
|
||||
|
||||
void init(std::size_t init_expand) {
|
||||
init_expand_ = init_expand;
|
||||
cursor_ = nullptr;
|
||||
}
|
||||
|
||||
static void** node_p(void* node) {
|
||||
return reinterpret_cast<void**>(node);
|
||||
}
|
||||
|
||||
static auto& next(void* node) {
|
||||
return *node_p(node);
|
||||
}
|
||||
|
||||
public:
|
||||
void swap(fixed_alloc_base& rhs) {
|
||||
std::swap(init_expand_, rhs.init_expand_);
|
||||
std::swap(cursor_ , rhs.cursor_);
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return cursor_ == nullptr;
|
||||
}
|
||||
|
||||
void take(fixed_alloc_base && rhs) {
|
||||
init_expand_ = (ipc::detail::max)(init_expand_, rhs.init_expand_);
|
||||
if (rhs.empty()) return;
|
||||
auto curr = cursor_;
|
||||
if (curr != nullptr) while (1) {
|
||||
auto next_cur = next(curr);
|
||||
if (next_cur == nullptr) {
|
||||
std::swap(next(curr), rhs.cursor_);
|
||||
return;
|
||||
}
|
||||
// next_cur != nullptr
|
||||
else curr = next_cur;
|
||||
}
|
||||
// curr == nullptr, means cursor_ == nullptr
|
||||
else std::swap(cursor_, rhs.cursor_);
|
||||
// rhs.cursor_ must be nullptr
|
||||
}
|
||||
|
||||
void free(void* p) {
|
||||
if (p == nullptr) return;
|
||||
next(p) = cursor_;
|
||||
cursor_ = p;
|
||||
}
|
||||
|
||||
void free(void* p, std::size_t) {
|
||||
free(p);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
struct fixed_expand_policy {
|
||||
|
||||
enum : std::size_t {
|
||||
base_size = sizeof(void*) * 1024
|
||||
};
|
||||
|
||||
constexpr static std::size_t prev(std::size_t e) noexcept {
|
||||
return ((e / 2) == 0) ? 1 : (e / 2);
|
||||
}
|
||||
|
||||
constexpr static std::size_t next(std::size_t e) noexcept {
|
||||
return e * 2;
|
||||
}
|
||||
|
||||
template <std::size_t BlockSize>
|
||||
static std::size_t next(std::size_t & e) {
|
||||
auto n = ipc::detail::max<std::size_t>(BlockSize, base_size) * e;
|
||||
e = next(e);
|
||||
return n;
|
||||
}
|
||||
};
|
||||
|
||||
template <std::size_t BlockSize,
|
||||
typename AllocP = scope_alloc<>,
|
||||
typename ExpandP = fixed_expand_policy>
|
||||
class fixed_alloc : public detail::fixed_alloc_base {
|
||||
public:
|
||||
using base_t = detail::fixed_alloc_base;
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
enum : std::size_t {
|
||||
block_size = (ipc::detail::max)(BlockSize, sizeof(void*))
|
||||
};
|
||||
|
||||
private:
|
||||
alloc_policy alloc_;
|
||||
|
||||
void* try_expand() {
|
||||
if (empty()) {
|
||||
auto size = ExpandP::template next<block_size>(init_expand_);
|
||||
auto p = node_p(cursor_ = alloc_.alloc(size));
|
||||
for (std::size_t i = 0; i < (size / block_size) - 1; ++i)
|
||||
p = node_p((*p) = reinterpret_cast<byte_t*>(p) + block_size);
|
||||
(*p) = nullptr;
|
||||
}
|
||||
return cursor_;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit fixed_alloc(std::size_t init_expand = 1) {
|
||||
init(init_expand);
|
||||
}
|
||||
|
||||
fixed_alloc(fixed_alloc&& rhs) : fixed_alloc() { swap(rhs); }
|
||||
fixed_alloc& operator=(fixed_alloc rhs) { swap(rhs); return (*this); }
|
||||
|
||||
template <typename A>
|
||||
void set_allocator(A && alc) {
|
||||
alloc_ = std::forward<A>(alc);
|
||||
}
|
||||
|
||||
void swap(fixed_alloc& rhs) {
|
||||
alloc_.swap(rhs.alloc_);
|
||||
base_t::swap(rhs);
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto take(fixed_alloc && rhs) -> ipc::require<detail::has_take<A>::value> {
|
||||
base_t::take(std::move(rhs));
|
||||
alloc_.take(std::move(rhs.alloc_));
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto take(fixed_alloc && rhs) -> ipc::require<!detail::has_take<A>::value> {
|
||||
base_t::take(std::move(rhs));
|
||||
}
|
||||
|
||||
void clear() {
|
||||
init_expand_ = ExpandP::prev(init_expand_);
|
||||
cursor_ = nullptr;
|
||||
alloc_.~alloc_policy();
|
||||
}
|
||||
|
||||
void* alloc() {
|
||||
void* p = try_expand();
|
||||
cursor_ = next(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
void* alloc(std::size_t) {
|
||||
return alloc();
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Variable-size blocks allocation (without alignment)
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
class variable_alloc_base {
|
||||
protected:
|
||||
struct head_t {
|
||||
std::size_t free_;
|
||||
} * head_ = nullptr;
|
||||
|
||||
std::map<std::size_t, head_t*, std::greater<std::size_t>> reserves_;
|
||||
|
||||
enum : std::size_t {
|
||||
aligned_head_size = aligned(sizeof(head_t), alignof(std::max_align_t))
|
||||
};
|
||||
|
||||
static byte_t * buffer(head_t* p) {
|
||||
return reinterpret_cast<byte_t*>(p) + aligned_head_size + p->free_;
|
||||
}
|
||||
|
||||
public:
|
||||
void swap(variable_alloc_base& rhs) {
|
||||
std::swap(head_, rhs.head_);
|
||||
}
|
||||
|
||||
std::size_t remain() const noexcept {
|
||||
return (head_ == nullptr) ? 0 : head_->free_;
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return remain() == 0;
|
||||
}
|
||||
|
||||
void take(variable_alloc_base && rhs) {
|
||||
if (rhs.remain() > remain()) {
|
||||
if (!empty()) {
|
||||
reserves_.emplace(head_->free_, head_);
|
||||
}
|
||||
head_ = rhs.head_;
|
||||
}
|
||||
else if (!rhs.empty()) {
|
||||
reserves_.emplace(rhs.head_->free_, rhs.head_);
|
||||
}
|
||||
rhs.head_ = nullptr;
|
||||
}
|
||||
|
||||
void free(void* /*p*/) {}
|
||||
void free(void* /*p*/, std::size_t) {}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <std::size_t ChunkSize = (sizeof(void*) * 1024), typename AllocP = scope_alloc<>>
|
||||
class variable_alloc : public detail::variable_alloc_base {
|
||||
public:
|
||||
using base_t = detail::variable_alloc_base;
|
||||
using head_t = base_t::head_t;
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
private:
|
||||
alloc_policy alloc_;
|
||||
|
||||
public:
|
||||
variable_alloc() = default;
|
||||
|
||||
variable_alloc(variable_alloc&& rhs) { swap(rhs); }
|
||||
variable_alloc& operator=(variable_alloc rhs) { swap(rhs); return (*this); }
|
||||
|
||||
template <typename A>
|
||||
void set_allocator(A && alc) {
|
||||
alloc_ = std::forward<A>(alc);
|
||||
}
|
||||
|
||||
void swap(variable_alloc& rhs) {
|
||||
alloc_.swap(rhs.alloc_);
|
||||
base_t::swap(rhs);
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto take(variable_alloc && rhs) -> ipc::require<detail::has_take<A>::value> {
|
||||
base_t::take(std::move(rhs));
|
||||
alloc_.take(std::move(rhs.alloc_));
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto take(variable_alloc && rhs) -> ipc::require<!detail::has_take<A>::value> {
|
||||
base_t::take(std::move(rhs));
|
||||
}
|
||||
|
||||
void clear() {
|
||||
alloc_.~alloc_policy();
|
||||
}
|
||||
|
||||
void* alloc(std::size_t size) {
|
||||
if (size >= ChunkSize) {
|
||||
return alloc_.alloc(size);
|
||||
}
|
||||
if (remain() < size) {
|
||||
auto it = reserves_.begin();
|
||||
if ((it == reserves_.end()) || (it->first < size)) {
|
||||
head_ = static_cast<head_t*>(alloc_.alloc(ChunkSize + aligned_head_size));
|
||||
head_->free_ = ChunkSize - size;
|
||||
}
|
||||
else {
|
||||
auto temp = it->second;
|
||||
temp->free_ -= size;
|
||||
reserves_.erase(it);
|
||||
if (remain() < temp->free_) {
|
||||
head_ = temp;
|
||||
}
|
||||
else return base_t::buffer(temp);
|
||||
}
|
||||
}
|
||||
// size shouldn't be 0 here, otherwise behavior is undefined
|
||||
else head_->free_ -= size;
|
||||
return base_t::buffer(head_);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
|
||||
142
src/memory/resource.h
Executable file → Normal file
142
src/memory/resource.h
Executable file → Normal file
@ -1,68 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
|
||||
#include "def.h"
|
||||
|
||||
#include "memory/alloc.h"
|
||||
#include "memory/wrapper.h"
|
||||
#include "platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
using chunk_variable_alloc = variable_alloc<sizeof(void*) * 256 * 1024 /* 2MB(x64) */>;
|
||||
|
||||
template <std::size_t Size>
|
||||
using static_async_fixed = static_wrapper<async_wrapper<fixed_alloc<Size, chunk_variable_alloc>>>;
|
||||
|
||||
using async_pool_alloc = static_alloc/*variable_wrapper<static_async_fixed>*/;
|
||||
|
||||
template <typename T>
|
||||
using allocator = allocator_wrapper<T, async_pool_alloc>;
|
||||
|
||||
} // namespace mem
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char const * pf(int) { return "%d" ; }
|
||||
constexpr char const * pf(long) { return "%ld" ; }
|
||||
constexpr char const * pf(long long) { return "%lld"; }
|
||||
constexpr char const * pf(unsigned int) { return "%u" ; }
|
||||
constexpr char const * pf(unsigned long) { return "%lu" ; }
|
||||
constexpr char const * pf(unsigned long long) { return "%llu"; }
|
||||
constexpr char const * pf(float) { return "%f" ; }
|
||||
constexpr char const * pf(double) { return "%f" ; }
|
||||
constexpr char const * pf(long double) { return "%Lf" ; }
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
template <typename Key, typename T>
|
||||
using unordered_map = std::unordered_map<
|
||||
Key, T, std::hash<Key>, std::equal_to<Key>, ipc::mem::allocator<std::pair<const Key, T>>
|
||||
>;
|
||||
|
||||
template <typename Char>
|
||||
using basic_string = std::basic_string<
|
||||
Char, std::char_traits<Char>, ipc::mem::allocator<Char>
|
||||
>;
|
||||
|
||||
using string = basic_string<char>;
|
||||
using wstring = basic_string<wchar_t>;
|
||||
|
||||
template <typename T>
|
||||
ipc::string to_string(T val) {
|
||||
char buf[std::numeric_limits<T>::digits10 + 1] {};
|
||||
if (std::snprintf(buf, sizeof(buf), pf(val), val) > 0) {
|
||||
return buf;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
|
||||
#include "def.h"
|
||||
|
||||
#include "memory/alloc.h"
|
||||
#include "memory/wrapper.h"
|
||||
#include "platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
namespace detail {
|
||||
|
||||
using chunk_variable_alloc =
|
||||
static_wrapper<async_wrapper<variable_alloc<
|
||||
sizeof(void*) * 1024 * 256 /* 2MB(x64) */ >>>;
|
||||
|
||||
template <std::size_t Size>
|
||||
using static_async_fixed =
|
||||
static_wrapper<async_wrapper<fixed_alloc<
|
||||
Size, chunk_variable_alloc >>>;
|
||||
|
||||
using async_pool_alloc = /*static_alloc*/variable_wrapper<static_async_fixed>;
|
||||
|
||||
template <typename T>
|
||||
using allocator = allocator_wrapper<T, async_pool_alloc>;
|
||||
|
||||
} // namespace mem
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char const * pf(int) { return "%d" ; }
|
||||
constexpr char const * pf(long) { return "%ld" ; }
|
||||
constexpr char const * pf(long long) { return "%lld"; }
|
||||
constexpr char const * pf(unsigned int) { return "%u" ; }
|
||||
constexpr char const * pf(unsigned long) { return "%lu" ; }
|
||||
constexpr char const * pf(unsigned long long) { return "%llu"; }
|
||||
constexpr char const * pf(float) { return "%f" ; }
|
||||
constexpr char const * pf(double) { return "%f" ; }
|
||||
constexpr char const * pf(long double) { return "%Lf" ; }
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
template <typename Key, typename T>
|
||||
using unordered_map = std::unordered_map<
|
||||
Key, T, std::hash<Key>, std::equal_to<Key>, ipc::mem::allocator<std::pair<const Key, T>>
|
||||
>;
|
||||
|
||||
template <typename Char>
|
||||
using basic_string = std::basic_string<
|
||||
Char, std::char_traits<Char>, ipc::mem::allocator<Char>
|
||||
>;
|
||||
|
||||
using string = basic_string<char>;
|
||||
using wstring = basic_string<wchar_t>;
|
||||
|
||||
template <typename T>
|
||||
ipc::string to_string(T val) {
|
||||
char buf[std::numeric_limits<T>::digits10 + 1] {};
|
||||
if (std::snprintf(buf, sizeof(buf), pf(val), val) > 0) {
|
||||
return buf;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
@ -1,379 +1,403 @@
|
||||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
#include <new>
|
||||
#include <tuple>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
|
||||
#include "def.h"
|
||||
#include "rw_lock.h"
|
||||
#include "tls_pointer.h"
|
||||
#include "concept.h"
|
||||
|
||||
#include "memory/alloc.h"
|
||||
#include "platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// The allocator wrapper class for STL
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename T, typename AllocP>
|
||||
class allocator_wrapper {
|
||||
|
||||
template <typename U, typename AllocU>
|
||||
friend class allocator_wrapper;
|
||||
|
||||
public:
|
||||
// type definitions
|
||||
typedef T value_type;
|
||||
typedef value_type* pointer;
|
||||
typedef const value_type* const_pointer;
|
||||
typedef value_type& reference;
|
||||
typedef const value_type& const_reference;
|
||||
typedef std::size_t size_type;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
typedef AllocP alloc_policy;
|
||||
|
||||
private:
|
||||
alloc_policy alloc_;
|
||||
|
||||
public:
|
||||
allocator_wrapper(void) noexcept = default;
|
||||
|
||||
allocator_wrapper(const allocator_wrapper<T, AllocP>& rhs) noexcept
|
||||
: alloc_(rhs.alloc_)
|
||||
{}
|
||||
|
||||
template <typename U>
|
||||
allocator_wrapper(const allocator_wrapper<U, AllocP>& rhs) noexcept
|
||||
: alloc_(rhs.alloc_)
|
||||
{}
|
||||
|
||||
allocator_wrapper(allocator_wrapper<T, AllocP>&& rhs) noexcept
|
||||
: alloc_(std::move(rhs.alloc_))
|
||||
{}
|
||||
|
||||
template <typename U>
|
||||
allocator_wrapper(allocator_wrapper<U, AllocP>&& rhs) noexcept
|
||||
: alloc_(std::move(rhs.alloc_))
|
||||
{}
|
||||
|
||||
allocator_wrapper(const AllocP& rhs) noexcept
|
||||
: alloc_(rhs)
|
||||
{}
|
||||
|
||||
allocator_wrapper(AllocP&& rhs) noexcept
|
||||
: alloc_(std::move(rhs))
|
||||
{}
|
||||
|
||||
public:
|
||||
// the other type of std_allocator
|
||||
template <typename U>
|
||||
struct rebind { typedef allocator_wrapper<U, AllocP> other; };
|
||||
|
||||
constexpr size_type max_size(void) const noexcept {
|
||||
return (std::numeric_limits<size_type>::max)() / sizeof(T);
|
||||
}
|
||||
|
||||
public:
|
||||
pointer allocate(size_type count) noexcept {
|
||||
if (count == 0) return nullptr;
|
||||
if (count > this->max_size()) return nullptr;
|
||||
return static_cast<pointer>(alloc_.alloc(count * sizeof(T)));
|
||||
}
|
||||
|
||||
void deallocate(pointer p, size_type count) noexcept {
|
||||
alloc_.free(p, count * sizeof(T));
|
||||
}
|
||||
|
||||
template <typename... P>
|
||||
static void construct(pointer p, P&&... params) {
|
||||
::new (static_cast<void*>(p)) T(std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
static void destroy(pointer p) {
|
||||
p->~T();
|
||||
}
|
||||
};
|
||||
|
||||
template <class AllocP>
|
||||
class allocator_wrapper<void, AllocP> {
|
||||
public:
|
||||
// type definitions
|
||||
typedef void value_type;
|
||||
typedef value_type* pointer;
|
||||
typedef const value_type* const_pointer;
|
||||
typedef std::size_t size_type;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
typedef AllocP alloc_policy;
|
||||
};
|
||||
|
||||
template <typename T, typename U, class AllocP>
|
||||
constexpr bool operator==(const allocator_wrapper<T, AllocP>&, const allocator_wrapper<U, AllocP>&) noexcept {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T, typename U, class AllocP>
|
||||
constexpr bool operator!=(const allocator_wrapper<T, AllocP>&, const allocator_wrapper<U, AllocP>&) noexcept {
|
||||
return false;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Thread-safe allocation wrapper
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename AllocP>
|
||||
class default_alloc_recoverer {
|
||||
public:
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
private:
|
||||
ipc::spin_lock master_lock_;
|
||||
std::vector<alloc_policy> master_allocs_;
|
||||
|
||||
public:
|
||||
void swap(default_alloc_recoverer& rhs) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(master_lock_);
|
||||
master_allocs_.swap(rhs.master_allocs_);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(master_lock_);
|
||||
master_allocs_.clear();
|
||||
}
|
||||
|
||||
void try_recover(alloc_policy & alc) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(master_lock_);
|
||||
if (!master_allocs_.empty()) {
|
||||
alc.swap(master_allocs_.back());
|
||||
master_allocs_.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto try_replenish(alloc_policy & alc) -> ipc::require<detail::has_take<A>::value> {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(master_lock_);
|
||||
if (!master_allocs_.empty()) {
|
||||
alc.take(std::move(master_allocs_.back()));
|
||||
master_allocs_.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
constexpr auto try_replenish(alloc_policy & /*alc*/) noexcept
|
||||
-> ipc::require<!detail::has_take<A>::value> {}
|
||||
|
||||
void collect(alloc_policy && alc) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(master_lock_);
|
||||
master_allocs_.emplace_back(std::move(alc));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename AllocP,
|
||||
template <typename> class RecovererP = default_alloc_recoverer>
|
||||
class async_wrapper {
|
||||
public:
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
private:
|
||||
RecovererP<alloc_policy> recoverer_;
|
||||
|
||||
class alloc_proxy : public AllocP {
|
||||
async_wrapper * w_ = nullptr;
|
||||
|
||||
IPC_CONCEPT_(has_empty, empty());
|
||||
|
||||
public:
|
||||
alloc_proxy(alloc_proxy && rhs)
|
||||
: AllocP(std::move(rhs))
|
||||
{}
|
||||
|
||||
alloc_proxy(async_wrapper* w)
|
||||
: AllocP(), w_(w) {
|
||||
if (w_ == nullptr) return;
|
||||
w_->recoverer_.try_recover(*this);
|
||||
}
|
||||
|
||||
~alloc_proxy() {
|
||||
if (w_ == nullptr) return;
|
||||
w_->recoverer_.collect(std::move(*this));
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto alloc(std::size_t size) -> ipc::require<has_empty<A>::value, void*> {
|
||||
auto p = AllocP::alloc(size);
|
||||
if (AllocP::empty() && (w_ != nullptr)) {
|
||||
w_->recoverer_.try_replenish(*this);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto alloc(std::size_t size) -> ipc::require<!has_empty<A>::value, void*> {
|
||||
return AllocP::alloc(size);
|
||||
}
|
||||
};
|
||||
|
||||
friend class alloc_proxy;
|
||||
|
||||
auto& get_alloc() {
|
||||
static tls::pointer<alloc_proxy> tls_alc;
|
||||
return *tls_alc.create(this);
|
||||
}
|
||||
|
||||
public:
|
||||
void swap(async_wrapper& rhs) {
|
||||
recoverer_.swap(rhs.recoverer_);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
recoverer_.clear();
|
||||
}
|
||||
|
||||
void* alloc(std::size_t size) {
|
||||
return get_alloc().alloc(size);
|
||||
}
|
||||
|
||||
void free(void* p, std::size_t size) {
|
||||
get_alloc().free(p, size);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Thread-safe allocation wrapper (with spin_lock)
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename AllocP, typename MutexT = ipc::spin_lock>
|
||||
class sync_wrapper {
|
||||
public:
|
||||
using alloc_policy = AllocP;
|
||||
using mutex_type = MutexT;
|
||||
|
||||
private:
|
||||
mutex_type lock_;
|
||||
alloc_policy alloc_;
|
||||
|
||||
public:
|
||||
void swap(sync_wrapper& rhs) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
alloc_.swap(rhs.alloc_);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
alloc_.~alloc_policy();
|
||||
}
|
||||
|
||||
void* alloc(std::size_t size) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
return alloc_.alloc(size);
|
||||
}
|
||||
|
||||
void free(void* p, std::size_t size) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
alloc_.free(p, size);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Static allocation wrapper
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename AllocP>
|
||||
class static_wrapper {
|
||||
public:
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
static alloc_policy& instance() {
|
||||
static alloc_policy alloc;
|
||||
return alloc;
|
||||
}
|
||||
|
||||
static void swap(static_wrapper&) {}
|
||||
|
||||
static void clear() {
|
||||
instance().clear();
|
||||
}
|
||||
|
||||
static void* alloc(std::size_t size) {
|
||||
return instance().alloc(size);
|
||||
}
|
||||
|
||||
static void free(void* p, std::size_t size) {
|
||||
instance().free(p, size);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Variable memory allocation wrapper
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <std::size_t BaseSize = sizeof(void*)>
|
||||
struct default_mapping_policy {
|
||||
|
||||
enum : std::size_t {
|
||||
base_size = BaseSize,
|
||||
classes_size = 32
|
||||
};
|
||||
|
||||
static const std::size_t table[classes_size];
|
||||
|
||||
constexpr static std::size_t classify(std::size_t size) {
|
||||
return (((size - 1) / base_size) < classes_size) ?
|
||||
// always uses default_mapping_policy<sizeof(void*)>::table
|
||||
default_mapping_policy<>::table[((size - 1) / base_size)] : classes_size;
|
||||
}
|
||||
};
|
||||
|
||||
template <std::size_t B>
|
||||
const std::size_t default_mapping_policy<B>::table[default_mapping_policy<B>::classes_size] = {
|
||||
/* 1 - 8 ~ 32 */
|
||||
0 , 1 , 2 , 3 ,
|
||||
/* 2 - 48 ~ 256 */
|
||||
5 , 5 , 7 , 7 , 9 , 9 , 11, 11, 13, 13, 15, 15, 17, 17,
|
||||
19, 19, 21, 21, 23, 23, 25, 25, 27, 27, 29, 29, 31, 31
|
||||
};
|
||||
|
||||
template <template <std::size_t> class Fixed,
|
||||
typename MappingP = default_mapping_policy<>,
|
||||
typename StaticAlloc = mem::static_alloc>
|
||||
class variable_wrapper {
|
||||
|
||||
template <typename F>
|
||||
constexpr static auto choose(std::size_t size, F&& f) {
|
||||
return ipc::detail::static_switch<MappingP::classes_size>(MappingP::classify(size), [&f](auto index) {
|
||||
return f(Fixed<(decltype(index)::value + 1) * MappingP::base_size>{});
|
||||
}, [&f] {
|
||||
return f(StaticAlloc{});
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
static void swap(variable_wrapper&) {}
|
||||
|
||||
static void clear() {
|
||||
ipc::detail::static_for<MappingP::classes_size>([](auto index) {
|
||||
Fixed<(decltype(index)::value + 1) * MappingP::base_size>::clear();
|
||||
});
|
||||
StaticAlloc::clear();
|
||||
}
|
||||
|
||||
static void* alloc(std::size_t size) {
|
||||
return choose(size, [size](auto&& alc) { return alc.alloc(size); });
|
||||
}
|
||||
|
||||
static void free(void* p, std::size_t size) {
|
||||
choose(size, [p, size](auto&& alc) { alc.free(p, size); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
#include <new>
|
||||
#include <tuple>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
|
||||
#include "def.h"
|
||||
#include "rw_lock.h"
|
||||
#include "tls_pointer.h"
|
||||
#include "concept.h"
|
||||
|
||||
#include "memory/alloc.h"
|
||||
#include "platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// The allocator wrapper class for STL
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename T, typename AllocP>
|
||||
class allocator_wrapper {
|
||||
|
||||
template <typename U, typename AllocU>
|
||||
friend class allocator_wrapper;
|
||||
|
||||
public:
|
||||
// type definitions
|
||||
typedef T value_type;
|
||||
typedef value_type* pointer;
|
||||
typedef const value_type* const_pointer;
|
||||
typedef value_type& reference;
|
||||
typedef const value_type& const_reference;
|
||||
typedef std::size_t size_type;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
typedef AllocP alloc_policy;
|
||||
|
||||
private:
|
||||
alloc_policy alloc_;
|
||||
|
||||
public:
|
||||
allocator_wrapper(void) noexcept = default;
|
||||
|
||||
allocator_wrapper(const allocator_wrapper<T, AllocP>& rhs) noexcept
|
||||
: alloc_(rhs.alloc_)
|
||||
{}
|
||||
|
||||
template <typename U>
|
||||
allocator_wrapper(const allocator_wrapper<U, AllocP>& rhs) noexcept
|
||||
: alloc_(rhs.alloc_)
|
||||
{}
|
||||
|
||||
allocator_wrapper(allocator_wrapper<T, AllocP>&& rhs) noexcept
|
||||
: alloc_(std::move(rhs.alloc_))
|
||||
{}
|
||||
|
||||
template <typename U>
|
||||
allocator_wrapper(allocator_wrapper<U, AllocP>&& rhs) noexcept
|
||||
: alloc_(std::move(rhs.alloc_))
|
||||
{}
|
||||
|
||||
allocator_wrapper(const AllocP& rhs) noexcept
|
||||
: alloc_(rhs)
|
||||
{}
|
||||
|
||||
allocator_wrapper(AllocP&& rhs) noexcept
|
||||
: alloc_(std::move(rhs))
|
||||
{}
|
||||
|
||||
public:
|
||||
// the other type of std_allocator
|
||||
template <typename U>
|
||||
struct rebind { typedef allocator_wrapper<U, AllocP> other; };
|
||||
|
||||
constexpr size_type max_size(void) const noexcept {
|
||||
return (std::numeric_limits<size_type>::max)() / sizeof(T);
|
||||
}
|
||||
|
||||
public:
|
||||
pointer allocate(size_type count) noexcept {
|
||||
if (count == 0) return nullptr;
|
||||
if (count > this->max_size()) return nullptr;
|
||||
return static_cast<pointer>(alloc_.alloc(count * sizeof(T)));
|
||||
}
|
||||
|
||||
void deallocate(pointer p, size_type count) noexcept {
|
||||
alloc_.free(p, count * sizeof(T));
|
||||
}
|
||||
|
||||
template <typename... P>
|
||||
static void construct(pointer p, P&&... params) {
|
||||
::new (static_cast<void*>(p)) T(std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
static void destroy(pointer p) {
|
||||
p->~T();
|
||||
}
|
||||
};
|
||||
|
||||
template <class AllocP>
|
||||
class allocator_wrapper<void, AllocP> {
|
||||
public:
|
||||
// type definitions
|
||||
typedef void value_type;
|
||||
typedef value_type* pointer;
|
||||
typedef const value_type* const_pointer;
|
||||
typedef std::size_t size_type;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
typedef AllocP alloc_policy;
|
||||
};
|
||||
|
||||
template <typename T, typename U, class AllocP>
|
||||
constexpr bool operator==(const allocator_wrapper<T, AllocP>&, const allocator_wrapper<U, AllocP>&) noexcept {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T, typename U, class AllocP>
|
||||
constexpr bool operator!=(const allocator_wrapper<T, AllocP>&, const allocator_wrapper<U, AllocP>&) noexcept {
|
||||
return false;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Thread-safe allocation wrapper
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename AllocP>
|
||||
class default_alloc_recycler {
|
||||
public:
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
private:
|
||||
ipc::spin_lock master_lock_;
|
||||
std::vector<alloc_policy> master_allocs_;
|
||||
|
||||
IPC_CONCEPT_(has_remain, remain());
|
||||
IPC_CONCEPT_(has_empty , empty());
|
||||
|
||||
public:
|
||||
void swap(default_alloc_recycler& rhs) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(master_lock_);
|
||||
master_allocs_.swap(rhs.master_allocs_);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(master_lock_);
|
||||
master_allocs_.clear();
|
||||
}
|
||||
|
||||
void try_recover(alloc_policy & alc) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(master_lock_);
|
||||
if (!master_allocs_.empty()) {
|
||||
alc.swap(master_allocs_.back());
|
||||
master_allocs_.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto try_replenish(alloc_policy & alc, std::size_t size)
|
||||
-> ipc::require<detail::has_take<A>::value && has_remain<A>::value> {
|
||||
if (alc.remain() >= size) return;
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(master_lock_);
|
||||
if (!master_allocs_.empty()) {
|
||||
alc.take(std::move(master_allocs_.back()));
|
||||
master_allocs_.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto try_replenish(alloc_policy & alc, std::size_t /*size*/)
|
||||
-> ipc::require<detail::has_take<A>::value && !has_remain<A>::value && has_empty<A>::value> {
|
||||
if (!alc.empty()) return;
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(master_lock_);
|
||||
if (!master_allocs_.empty()) {
|
||||
alc.take(std::move(master_allocs_.back()));
|
||||
master_allocs_.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
constexpr auto try_replenish(alloc_policy & /*alc*/, std::size_t /*size*/) noexcept
|
||||
-> ipc::require<!detail::has_take<A>::value || (!has_remain<A>::value && !has_empty<A>::value)> {}
|
||||
|
||||
void collect(alloc_policy && alc) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(master_lock_);
|
||||
master_allocs_.emplace_back(std::move(alc));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename AllocP>
|
||||
class empty_alloc_recycler {
|
||||
public:
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
constexpr static void swap(empty_alloc_recycler&) noexcept {}
|
||||
constexpr static void clear() noexcept {}
|
||||
constexpr static void try_recover(alloc_policy&) noexcept {}
|
||||
constexpr static auto try_replenish(alloc_policy&, std::size_t) noexcept {}
|
||||
constexpr static void collect(alloc_policy&&) noexcept {}
|
||||
};
|
||||
|
||||
template <typename AllocP,
|
||||
template <typename> class RecyclerP = default_alloc_recycler>
|
||||
class async_wrapper {
|
||||
public:
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
private:
|
||||
RecyclerP<alloc_policy> recycler_;
|
||||
|
||||
class alloc_proxy : public AllocP {
|
||||
async_wrapper * w_ = nullptr;
|
||||
|
||||
public:
|
||||
alloc_proxy(alloc_proxy && rhs)
|
||||
: AllocP(std::move(rhs))
|
||||
{}
|
||||
|
||||
alloc_proxy(async_wrapper* w)
|
||||
: AllocP(), w_(w) {
|
||||
if (w_ == nullptr) return;
|
||||
w_->recycler_.try_recover(*this);
|
||||
}
|
||||
|
||||
~alloc_proxy() {
|
||||
if (w_ == nullptr) return;
|
||||
w_->recycler_.collect(std::move(*this));
|
||||
}
|
||||
|
||||
auto alloc(std::size_t size) {
|
||||
if (w_ != nullptr) {
|
||||
w_->recycler_.try_replenish(*this, size);
|
||||
}
|
||||
return AllocP::alloc(size);
|
||||
}
|
||||
};
|
||||
|
||||
friend class alloc_proxy;
|
||||
|
||||
auto& get_alloc() {
|
||||
static tls::pointer<alloc_proxy> tls_alc;
|
||||
return *tls_alc.create(this);
|
||||
}
|
||||
|
||||
public:
|
||||
void swap(async_wrapper& rhs) {
|
||||
recycler_.swap(rhs.recycler_);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
recycler_.clear();
|
||||
}
|
||||
|
||||
void* alloc(std::size_t size) {
|
||||
return get_alloc().alloc(size);
|
||||
}
|
||||
|
||||
void free(void* p, std::size_t size) {
|
||||
get_alloc().free(p, size);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Thread-safe allocation wrapper (with spin_lock)
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename AllocP, typename MutexT = ipc::spin_lock>
|
||||
class sync_wrapper {
|
||||
public:
|
||||
using alloc_policy = AllocP;
|
||||
using mutex_type = MutexT;
|
||||
|
||||
private:
|
||||
mutex_type lock_;
|
||||
alloc_policy alloc_;
|
||||
|
||||
public:
|
||||
void swap(sync_wrapper& rhs) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
alloc_.swap(rhs.alloc_);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
alloc_.~alloc_policy();
|
||||
}
|
||||
|
||||
void* alloc(std::size_t size) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
return alloc_.alloc(size);
|
||||
}
|
||||
|
||||
void free(void* p, std::size_t size) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
alloc_.free(p, size);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Static allocation wrapper
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename AllocP>
|
||||
class static_wrapper {
|
||||
public:
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
static alloc_policy& instance() {
|
||||
static alloc_policy alloc;
|
||||
return alloc;
|
||||
}
|
||||
|
||||
static void swap(static_wrapper&) {}
|
||||
|
||||
static void clear() {
|
||||
instance().clear();
|
||||
}
|
||||
|
||||
static void* alloc(std::size_t size) {
|
||||
return instance().alloc(size);
|
||||
}
|
||||
|
||||
static void free(void* p, std::size_t size) {
|
||||
instance().free(p, size);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Variable memory allocation wrapper
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <std::size_t BaseSize = sizeof(void*)>
|
||||
struct default_mapping_policy {
|
||||
|
||||
enum : std::size_t {
|
||||
base_size = BaseSize,
|
||||
classes_size = 32
|
||||
};
|
||||
|
||||
static const std::size_t table[classes_size];
|
||||
|
||||
IPC_CONSTEXPR_ static std::size_t classify(std::size_t size) noexcept {
|
||||
auto index = (size - 1) / base_size;
|
||||
return (index < classes_size) ?
|
||||
// always uses default_mapping_policy<sizeof(void*)>::table
|
||||
default_mapping_policy<>::table[index] : classes_size;
|
||||
}
|
||||
|
||||
constexpr static std::size_t block_size(std::size_t value) noexcept {
|
||||
return (value + 1) * base_size;
|
||||
}
|
||||
};
|
||||
|
||||
template <std::size_t B>
|
||||
const std::size_t default_mapping_policy<B>::table[default_mapping_policy<B>::classes_size] = {
|
||||
/* 1 - 8 ~ 32 */
|
||||
0 , 1 , 2 , 3 ,
|
||||
/* 2 - 48 ~ 256 */
|
||||
5 , 5 , 7 , 7 , 9 , 9 , 11, 11, 13, 13, 15, 15, 17, 17,
|
||||
19, 19, 21, 21, 23, 23, 25, 25, 27, 27, 29, 29, 31, 31
|
||||
};
|
||||
|
||||
template <template <std::size_t> class Fixed,
|
||||
typename MappingP = default_mapping_policy<>,
|
||||
typename StaticAlloc = mem::static_alloc>
|
||||
class variable_wrapper {
|
||||
|
||||
template <typename F>
|
||||
constexpr static auto choose(std::size_t size, F&& f) {
|
||||
return ipc::detail::static_switch<MappingP::classes_size>(MappingP::classify(size), [&f](auto index) {
|
||||
return f(Fixed<MappingP::block_size(decltype(index)::value)>{});
|
||||
}, [&f] {
|
||||
return f(StaticAlloc{});
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
static void swap(variable_wrapper&) {}
|
||||
|
||||
static void clear() {
|
||||
ipc::detail::static_for<MappingP::classes_size>([](auto index) {
|
||||
Fixed<MappingP::block_size(decltype(index)::value)>::clear();
|
||||
});
|
||||
StaticAlloc::clear();
|
||||
}
|
||||
|
||||
static void* alloc(std::size_t size) {
|
||||
return choose(size, [size](auto&& alc) { return alc.alloc(size); });
|
||||
}
|
||||
|
||||
static void free(void* p, std::size_t size) {
|
||||
choose(size, [p, size](auto&& alc) { alc.free(p, size); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
|
||||
134
src/pimpl.h
134
src/pimpl.h
@ -1,67 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <new>
|
||||
#include <utility>
|
||||
|
||||
#include "concept.h"
|
||||
#include "pool_alloc.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
// pimpl small object optimization helpers
|
||||
|
||||
template <typename T, typename R = T*>
|
||||
using IsImplComfortable = ipc::require<(sizeof(T) <= sizeof(T*)), R>;
|
||||
|
||||
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> {
|
||||
T* buf {};
|
||||
::new (&buf) T { std::forward<P>(params)... };
|
||||
return buf;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
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> {
|
||||
if (p != nullptr) impl(p)->~T();
|
||||
}
|
||||
|
||||
template <typename T, typename... P>
|
||||
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> {
|
||||
mem::free(p);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr auto impl(T* const (& p)) -> IsImplUncomfortable<T> {
|
||||
return p;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct pimpl {
|
||||
template <typename... P>
|
||||
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*/
|
||||
clear_impl(static_cast<T*>(const_cast<pimpl*>(this)));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
#pragma once
|
||||
|
||||
#include <new>
|
||||
#include <utility>
|
||||
|
||||
#include "concept.h"
|
||||
#include "pool_alloc.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
// pimpl small object optimization helpers
|
||||
|
||||
template <typename T, typename R = T*>
|
||||
using IsImplComfortable = ipc::require<(sizeof(T) <= sizeof(T*)), R>;
|
||||
|
||||
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> {
|
||||
T* buf {};
|
||||
::new (&buf) T { std::forward<P>(params)... };
|
||||
return buf;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
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> {
|
||||
if (p != nullptr) impl(p)->~T();
|
||||
}
|
||||
|
||||
template <typename T, typename... P>
|
||||
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> {
|
||||
mem::free(p);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr auto impl(T* const (& p)) -> IsImplUncomfortable<T> {
|
||||
return p;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct pimpl {
|
||||
template <typename... P>
|
||||
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*/
|
||||
clear_impl(static_cast<T*>(const_cast<pimpl*>(this)));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
@ -1,149 +1,149 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <type_traits>
|
||||
#include <tuple>
|
||||
#include <atomic>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <new>
|
||||
|
||||
#include "def.h"
|
||||
#include "export.h"
|
||||
|
||||
// pre-defined
|
||||
|
||||
#ifdef IPC_UNUSED_
|
||||
# error "IPC_UNUSED_ has been defined."
|
||||
#endif
|
||||
#ifdef IPC_FALLTHROUGH_
|
||||
# error "IPC_FALLTHROUGH_ has been defined."
|
||||
#endif
|
||||
#ifdef IPC_STBIND_
|
||||
# error "IPC_STBIND_ has been defined."
|
||||
#endif
|
||||
#ifdef IPC_CONSTEXPR_
|
||||
# error "IPC_CONSTEXPR_ has been defined."
|
||||
#endif
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
|
||||
#define IPC_UNUSED_ [[maybe_unused]]
|
||||
#define IPC_FALLTHROUGH_ [[fallthrough]]
|
||||
#define IPC_STBIND_(A, B, ...) auto [A, B] = __VA_ARGS__
|
||||
#define IPC_CONSTEXPR_ constexpr
|
||||
|
||||
#else /*__cplusplus < 201703L*/
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# define IPC_UNUSED_ __pragma(warning(suppress: 4100 4101 4189))
|
||||
#elif defined(__GNUC__)
|
||||
# define IPC_UNUSED_ __attribute__((__unused__))
|
||||
#else
|
||||
# define IPC_UNUSED_
|
||||
#endif
|
||||
|
||||
#define IPC_FALLTHROUGH_
|
||||
|
||||
#define IPC_STBIND_(A, B, ...) \
|
||||
auto tp___ = __VA_ARGS__ \
|
||||
auto A = std::get<0>(tp___); \
|
||||
auto B = std::get<1>(tp___)
|
||||
|
||||
#define IPC_CONSTEXPR_ inline
|
||||
|
||||
#endif/*__cplusplus < 201703L*/
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
|
||||
namespace std {
|
||||
|
||||
// deduction guides for std::unique_ptr
|
||||
template <typename T, typename D>
|
||||
unique_ptr(T* p, D&& d) -> unique_ptr<T, std::decay_t<D>>;
|
||||
|
||||
} // namespace std
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
using std::unique_ptr;
|
||||
using std::unique_lock;
|
||||
using std::shared_lock;
|
||||
using std::max;
|
||||
using std::min;
|
||||
|
||||
#else /*__cplusplus < 201703L*/
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
// deduction guides for std::unique_ptr
|
||||
template <typename T, typename D>
|
||||
constexpr auto unique_ptr(T* p, D&& d) {
|
||||
return std::unique_ptr<T, std::decay_t<D>> { p, std::forward<D>(d) };
|
||||
}
|
||||
|
||||
// deduction guides for std::unique_lock
|
||||
template <typename T>
|
||||
constexpr auto unique_lock(T&& lc) {
|
||||
return std::unique_lock<std::decay_t<T>> { std::forward<T>(lc) };
|
||||
}
|
||||
|
||||
// deduction guides for std::shared_lock
|
||||
template <typename T>
|
||||
constexpr auto shared_lock(T&& lc) {
|
||||
return std::shared_lock<std::decay_t<T>> { std::forward<T>(lc) };
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr const T& (max)(const T& a, const T& b) {
|
||||
return (a < b) ? b : a;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr const T& (min)(const T& a, const T& b) {
|
||||
return (b < a) ? b : a;
|
||||
}
|
||||
|
||||
#endif/*__cplusplus < 201703L*/
|
||||
|
||||
template <typename F, typename D>
|
||||
constexpr decltype(auto) static_switch(std::size_t /*i*/, std::index_sequence<>, F&& /*f*/, D&& def) {
|
||||
return std::forward<D>(def)();
|
||||
}
|
||||
|
||||
template <typename F, typename D, std::size_t N, std::size_t...I>
|
||||
constexpr decltype(auto) static_switch(std::size_t i, std::index_sequence<N, I...>, F&& f, D&& def) {
|
||||
return (i == N) ? std::forward<F>(f)(std::integral_constant<size_t, N>{}) :
|
||||
static_switch(i, std::index_sequence<I...>{}, std::forward<F>(f), std::forward<D>(def));
|
||||
}
|
||||
|
||||
template <std::size_t N, typename F, typename D>
|
||||
constexpr decltype(auto) static_switch(std::size_t i, F&& f, D&& def) {
|
||||
return static_switch(i, std::make_index_sequence<N>{}, std::forward<F>(f), std::forward<D>(def));
|
||||
}
|
||||
|
||||
template <typename F, std::size_t...I>
|
||||
IPC_CONSTEXPR_ void static_for(std::index_sequence<I...>, F&& f) {
|
||||
IPC_UNUSED_ auto expand = { (std::forward<F>(f)(std::integral_constant<size_t, I>{}), 0)... };
|
||||
}
|
||||
|
||||
template <std::size_t N, typename F>
|
||||
IPC_CONSTEXPR_ void static_for(F&& f) {
|
||||
static_for(std::make_index_sequence<N>{}, std::forward<F>(f));
|
||||
}
|
||||
|
||||
// Minimum offset between two objects to avoid false sharing.
|
||||
enum {
|
||||
// #if __cplusplus >= 201703L
|
||||
// cache_line_size = std::hardware_destructive_interference_size
|
||||
// #else /*__cplusplus < 201703L*/
|
||||
cache_line_size = 64
|
||||
// #endif/*__cplusplus < 201703L*/
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <type_traits>
|
||||
#include <tuple>
|
||||
#include <atomic>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <new>
|
||||
|
||||
#include "def.h"
|
||||
#include "export.h"
|
||||
|
||||
// pre-defined
|
||||
|
||||
#ifdef IPC_UNUSED_
|
||||
# error "IPC_UNUSED_ has been defined."
|
||||
#endif
|
||||
#ifdef IPC_FALLTHROUGH_
|
||||
# error "IPC_FALLTHROUGH_ has been defined."
|
||||
#endif
|
||||
#ifdef IPC_STBIND_
|
||||
# error "IPC_STBIND_ has been defined."
|
||||
#endif
|
||||
#ifdef IPC_CONSTEXPR_
|
||||
# error "IPC_CONSTEXPR_ has been defined."
|
||||
#endif
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
|
||||
#define IPC_UNUSED_ [[maybe_unused]]
|
||||
#define IPC_FALLTHROUGH_ [[fallthrough]]
|
||||
#define IPC_STBIND_(A, B, ...) auto [A, B] = __VA_ARGS__
|
||||
#define IPC_CONSTEXPR_ constexpr
|
||||
|
||||
#else /*__cplusplus < 201703L*/
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# define IPC_UNUSED_ __pragma(warning(suppress: 4100 4101 4189))
|
||||
#elif defined(__GNUC__)
|
||||
# define IPC_UNUSED_ __attribute__((__unused__))
|
||||
#else
|
||||
# define IPC_UNUSED_
|
||||
#endif
|
||||
|
||||
#define IPC_FALLTHROUGH_
|
||||
|
||||
#define IPC_STBIND_(A, B, ...) \
|
||||
auto tp___ = __VA_ARGS__ \
|
||||
auto A = std::get<0>(tp___); \
|
||||
auto B = std::get<1>(tp___)
|
||||
|
||||
#define IPC_CONSTEXPR_ inline
|
||||
|
||||
#endif/*__cplusplus < 201703L*/
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
|
||||
namespace std {
|
||||
|
||||
// deduction guides for std::unique_ptr
|
||||
template <typename T, typename D>
|
||||
unique_ptr(T* p, D&& d) -> unique_ptr<T, std::decay_t<D>>;
|
||||
|
||||
} // namespace std
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
using std::unique_ptr;
|
||||
using std::unique_lock;
|
||||
using std::shared_lock;
|
||||
using std::max;
|
||||
using std::min;
|
||||
|
||||
#else /*__cplusplus < 201703L*/
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
// deduction guides for std::unique_ptr
|
||||
template <typename T, typename D>
|
||||
constexpr auto unique_ptr(T* p, D&& d) {
|
||||
return std::unique_ptr<T, std::decay_t<D>> { p, std::forward<D>(d) };
|
||||
}
|
||||
|
||||
// deduction guides for std::unique_lock
|
||||
template <typename T>
|
||||
constexpr auto unique_lock(T&& lc) {
|
||||
return std::unique_lock<std::decay_t<T>> { std::forward<T>(lc) };
|
||||
}
|
||||
|
||||
// deduction guides for std::shared_lock
|
||||
template <typename T>
|
||||
constexpr auto shared_lock(T&& lc) {
|
||||
return std::shared_lock<std::decay_t<T>> { std::forward<T>(lc) };
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr const T& (max)(const T& a, const T& b) {
|
||||
return (a < b) ? b : a;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr const T& (min)(const T& a, const T& b) {
|
||||
return (b < a) ? b : a;
|
||||
}
|
||||
|
||||
#endif/*__cplusplus < 201703L*/
|
||||
|
||||
template <typename F, typename D>
|
||||
constexpr decltype(auto) static_switch(std::size_t /*i*/, std::index_sequence<>, F&& /*f*/, D&& def) {
|
||||
return std::forward<D>(def)();
|
||||
}
|
||||
|
||||
template <typename F, typename D, std::size_t N, std::size_t...I>
|
||||
constexpr decltype(auto) static_switch(std::size_t i, std::index_sequence<N, I...>, F&& f, D&& def) {
|
||||
return (i == N) ? std::forward<F>(f)(std::integral_constant<size_t, N>{}) :
|
||||
static_switch(i, std::index_sequence<I...>{}, std::forward<F>(f), std::forward<D>(def));
|
||||
}
|
||||
|
||||
template <std::size_t N, typename F, typename D>
|
||||
constexpr decltype(auto) static_switch(std::size_t i, F&& f, D&& def) {
|
||||
return static_switch(i, std::make_index_sequence<N>{}, std::forward<F>(f), std::forward<D>(def));
|
||||
}
|
||||
|
||||
template <typename F, std::size_t...I>
|
||||
IPC_CONSTEXPR_ void static_for(std::index_sequence<I...>, F&& f) {
|
||||
IPC_UNUSED_ auto expand = { (std::forward<F>(f)(std::integral_constant<size_t, I>{}), 0)... };
|
||||
}
|
||||
|
||||
template <std::size_t N, typename F>
|
||||
IPC_CONSTEXPR_ void static_for(F&& f) {
|
||||
static_for(std::make_index_sequence<N>{}, std::forward<F>(f));
|
||||
}
|
||||
|
||||
// Minimum offset between two objects to avoid false sharing.
|
||||
enum {
|
||||
// #if __cplusplus >= 201703L
|
||||
// cache_line_size = std::hardware_destructive_interference_size
|
||||
// #else /*__cplusplus < 201703L*/
|
||||
cache_line_size = 64
|
||||
// #endif/*__cplusplus < 201703L*/
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
|
||||
@ -1,172 +1,172 @@
|
||||
#include "shm.h"
|
||||
|
||||
#include <sys/shm.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <cstring>
|
||||
|
||||
#include "def.h"
|
||||
#include "log.h"
|
||||
#include "pool_alloc.h"
|
||||
|
||||
#include "memory/resource.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct info_t {
|
||||
std::atomic_size_t acc_;
|
||||
};
|
||||
|
||||
struct id_info_t {
|
||||
int fd_ = -1;
|
||||
void* mem_ = nullptr;
|
||||
std::size_t size_ = 0;
|
||||
ipc::string name_;
|
||||
};
|
||||
|
||||
constexpr std::size_t calc_size(std::size_t size) {
|
||||
return ((((size - 1) / alignof(info_t)) + 1) * alignof(info_t)) + sizeof(info_t);
|
||||
}
|
||||
|
||||
inline auto& acc_of(void* mem, std::size_t size) {
|
||||
return reinterpret_cast<info_t*>(static_cast<ipc::byte_t*>(mem) + size - sizeof(info_t))->acc_;
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
namespace ipc {
|
||||
namespace shm {
|
||||
|
||||
id_t acquire(char const * name, std::size_t size, unsigned mode) {
|
||||
if (name == nullptr || name[0] == '\0') {
|
||||
ipc::error("fail acquire: name is empty\n");
|
||||
return nullptr;
|
||||
}
|
||||
ipc::string op_name = ipc::string{"__IPC_SHM__"} + name;
|
||||
// Open the object for read-write access.
|
||||
int flag = O_RDWR;
|
||||
switch (mode) {
|
||||
case open:
|
||||
size = 0;
|
||||
break;
|
||||
// The check for the existence of the object,
|
||||
// and its creation if it does not exist, are performed atomically.
|
||||
case create:
|
||||
flag |= O_CREAT | O_EXCL;
|
||||
break;
|
||||
// Create the shared memory object if it does not exist.
|
||||
default:
|
||||
flag |= O_CREAT;
|
||||
break;
|
||||
}
|
||||
int fd = ::shm_open(op_name.c_str(), flag, S_IRUSR | S_IWUSR |
|
||||
S_IRGRP | S_IWGRP |
|
||||
S_IROTH | S_IWOTH);
|
||||
if (fd == -1) {
|
||||
ipc::error("fail shm_open[%d]: %s\n", errno, name);
|
||||
return nullptr;
|
||||
}
|
||||
auto ii = mem::alloc<id_info_t>();
|
||||
ii->fd_ = fd;
|
||||
ii->size_ = size;
|
||||
ii->name_ = std::move(op_name);
|
||||
return ii;
|
||||
}
|
||||
|
||||
void * get_mem(id_t id, std::size_t * size) {
|
||||
if (id == nullptr) {
|
||||
ipc::error("fail get_mem: invalid id (null)\n");
|
||||
return nullptr;
|
||||
}
|
||||
auto ii = static_cast<id_info_t*>(id);
|
||||
if (ii->mem_ != nullptr) {
|
||||
if (size != nullptr) *size = ii->size_;
|
||||
return ii->mem_;
|
||||
}
|
||||
int fd = ii->fd_;
|
||||
if (fd == -1) {
|
||||
ipc::error("fail to_mem: invalid id (fd = -1)\n");
|
||||
return nullptr;
|
||||
}
|
||||
if (ii->size_ == 0) {
|
||||
struct stat st;
|
||||
if (::fstat(fd, &st) != 0) {
|
||||
ipc::error("fail fstat[%d]: %s, size = %zd\n", errno, ii->name_.c_str(), ii->size_);
|
||||
return nullptr;
|
||||
}
|
||||
ii->size_ = static_cast<std::size_t>(st.st_size);
|
||||
if ((ii->size_ <= sizeof(info_t)) || (ii->size_ % sizeof(info_t))) {
|
||||
ipc::error("fail to_mem: %s, invalid size = %zd\n", ii->name_.c_str(), ii->size_);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ii->size_ = calc_size(ii->size_);
|
||||
if (::ftruncate(fd, static_cast<off_t>(ii->size_)) != 0) {
|
||||
ipc::error("fail ftruncate[%d]: %s, size = %zd\n", errno, ii->name_.c_str(), ii->size_);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
void* mem = ::mmap(nullptr, ii->size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (mem == MAP_FAILED) {
|
||||
ipc::error("fail mmap[%d]: %s, size = %zd\n", errno, ii->name_.c_str(), ii->size_);
|
||||
return nullptr;
|
||||
}
|
||||
::close(fd);
|
||||
ii->fd_ = -1;
|
||||
ii->mem_ = mem;
|
||||
if (size != nullptr) *size = ii->size_;
|
||||
acc_of(mem, ii->size_).fetch_add(1, std::memory_order_release);
|
||||
return mem;
|
||||
}
|
||||
|
||||
void release(id_t id) {
|
||||
if (id == nullptr) {
|
||||
ipc::error("fail release: invalid id (null)\n");
|
||||
return;
|
||||
}
|
||||
auto ii = static_cast<id_info_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) {
|
||||
::munmap(ii->mem_, ii->size_);
|
||||
if (!ii->name_.empty()) {
|
||||
::shm_unlink(ii->name_.c_str());
|
||||
}
|
||||
}
|
||||
else ::munmap(ii->mem_, ii->size_);
|
||||
mem::free(ii);
|
||||
}
|
||||
|
||||
void remove(id_t id) {
|
||||
if (id == nullptr) {
|
||||
ipc::error("fail remove: invalid id (null)\n");
|
||||
return;
|
||||
}
|
||||
auto ii = static_cast<id_info_t*>(id);
|
||||
auto name = std::move(ii->name_);
|
||||
release(id);
|
||||
if (!name.empty()) {
|
||||
::shm_unlink(name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void remove(char const * name) {
|
||||
if (name == nullptr || name[0] == '\0') {
|
||||
ipc::error("fail remove: name is empty\n");
|
||||
return;
|
||||
}
|
||||
::shm_unlink((ipc::string{"__IPC_SHM__"} + name).c_str());
|
||||
}
|
||||
|
||||
} // namespace shm
|
||||
} // namespace ipc
|
||||
#include "shm.h"
|
||||
|
||||
#include <sys/shm.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <cstring>
|
||||
|
||||
#include "def.h"
|
||||
#include "log.h"
|
||||
#include "pool_alloc.h"
|
||||
|
||||
#include "memory/resource.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct info_t {
|
||||
std::atomic_size_t acc_;
|
||||
};
|
||||
|
||||
struct id_info_t {
|
||||
int fd_ = -1;
|
||||
void* mem_ = nullptr;
|
||||
std::size_t size_ = 0;
|
||||
ipc::string name_;
|
||||
};
|
||||
|
||||
constexpr std::size_t calc_size(std::size_t size) {
|
||||
return ((((size - 1) / alignof(info_t)) + 1) * alignof(info_t)) + sizeof(info_t);
|
||||
}
|
||||
|
||||
inline auto& acc_of(void* mem, std::size_t size) {
|
||||
return reinterpret_cast<info_t*>(static_cast<ipc::byte_t*>(mem) + size - sizeof(info_t))->acc_;
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
namespace ipc {
|
||||
namespace shm {
|
||||
|
||||
id_t acquire(char const * name, std::size_t size, unsigned mode) {
|
||||
if (name == nullptr || name[0] == '\0') {
|
||||
ipc::error("fail acquire: name is empty\n");
|
||||
return nullptr;
|
||||
}
|
||||
ipc::string op_name = ipc::string{"__IPC_SHM__"} + name;
|
||||
// Open the object for read-write access.
|
||||
int flag = O_RDWR;
|
||||
switch (mode) {
|
||||
case open:
|
||||
size = 0;
|
||||
break;
|
||||
// The check for the existence of the object,
|
||||
// and its creation if it does not exist, are performed atomically.
|
||||
case create:
|
||||
flag |= O_CREAT | O_EXCL;
|
||||
break;
|
||||
// Create the shared memory object if it does not exist.
|
||||
default:
|
||||
flag |= O_CREAT;
|
||||
break;
|
||||
}
|
||||
int fd = ::shm_open(op_name.c_str(), flag, S_IRUSR | S_IWUSR |
|
||||
S_IRGRP | S_IWGRP |
|
||||
S_IROTH | S_IWOTH);
|
||||
if (fd == -1) {
|
||||
ipc::error("fail shm_open[%d]: %s\n", errno, name);
|
||||
return nullptr;
|
||||
}
|
||||
auto ii = mem::alloc<id_info_t>();
|
||||
ii->fd_ = fd;
|
||||
ii->size_ = size;
|
||||
ii->name_ = std::move(op_name);
|
||||
return ii;
|
||||
}
|
||||
|
||||
void * get_mem(id_t id, std::size_t * size) {
|
||||
if (id == nullptr) {
|
||||
ipc::error("fail get_mem: invalid id (null)\n");
|
||||
return nullptr;
|
||||
}
|
||||
auto ii = static_cast<id_info_t*>(id);
|
||||
if (ii->mem_ != nullptr) {
|
||||
if (size != nullptr) *size = ii->size_;
|
||||
return ii->mem_;
|
||||
}
|
||||
int fd = ii->fd_;
|
||||
if (fd == -1) {
|
||||
ipc::error("fail to_mem: invalid id (fd = -1)\n");
|
||||
return nullptr;
|
||||
}
|
||||
if (ii->size_ == 0) {
|
||||
struct stat st;
|
||||
if (::fstat(fd, &st) != 0) {
|
||||
ipc::error("fail fstat[%d]: %s, size = %zd\n", errno, ii->name_.c_str(), ii->size_);
|
||||
return nullptr;
|
||||
}
|
||||
ii->size_ = static_cast<std::size_t>(st.st_size);
|
||||
if ((ii->size_ <= sizeof(info_t)) || (ii->size_ % sizeof(info_t))) {
|
||||
ipc::error("fail to_mem: %s, invalid size = %zd\n", ii->name_.c_str(), ii->size_);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ii->size_ = calc_size(ii->size_);
|
||||
if (::ftruncate(fd, static_cast<off_t>(ii->size_)) != 0) {
|
||||
ipc::error("fail ftruncate[%d]: %s, size = %zd\n", errno, ii->name_.c_str(), ii->size_);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
void* mem = ::mmap(nullptr, ii->size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (mem == MAP_FAILED) {
|
||||
ipc::error("fail mmap[%d]: %s, size = %zd\n", errno, ii->name_.c_str(), ii->size_);
|
||||
return nullptr;
|
||||
}
|
||||
::close(fd);
|
||||
ii->fd_ = -1;
|
||||
ii->mem_ = mem;
|
||||
if (size != nullptr) *size = ii->size_;
|
||||
acc_of(mem, ii->size_).fetch_add(1, std::memory_order_release);
|
||||
return mem;
|
||||
}
|
||||
|
||||
void release(id_t id) {
|
||||
if (id == nullptr) {
|
||||
ipc::error("fail release: invalid id (null)\n");
|
||||
return;
|
||||
}
|
||||
auto ii = static_cast<id_info_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) {
|
||||
::munmap(ii->mem_, ii->size_);
|
||||
if (!ii->name_.empty()) {
|
||||
::shm_unlink(ii->name_.c_str());
|
||||
}
|
||||
}
|
||||
else ::munmap(ii->mem_, ii->size_);
|
||||
mem::free(ii);
|
||||
}
|
||||
|
||||
void remove(id_t id) {
|
||||
if (id == nullptr) {
|
||||
ipc::error("fail remove: invalid id (null)\n");
|
||||
return;
|
||||
}
|
||||
auto ii = static_cast<id_info_t*>(id);
|
||||
auto name = std::move(ii->name_);
|
||||
release(id);
|
||||
if (!name.empty()) {
|
||||
::shm_unlink(name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void remove(char const * name) {
|
||||
if (name == nullptr || name[0] == '\0') {
|
||||
ipc::error("fail remove: name is empty\n");
|
||||
return;
|
||||
}
|
||||
::shm_unlink((ipc::string{"__IPC_SHM__"} + name).c_str());
|
||||
}
|
||||
|
||||
} // namespace shm
|
||||
} // namespace ipc
|
||||
|
||||
@ -1,124 +1,124 @@
|
||||
#include "shm.h"
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "def.h"
|
||||
#include "log.h"
|
||||
#include "pool_alloc.h"
|
||||
|
||||
#include "platform/to_tchar.h"
|
||||
#include "memory/resource.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct id_info_t {
|
||||
HANDLE h_ = NULL;
|
||||
void* mem_ = nullptr;
|
||||
std::size_t size_ = 0;
|
||||
};
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
namespace ipc {
|
||||
namespace shm {
|
||||
|
||||
id_t acquire(char const * name, std::size_t size, unsigned mode) {
|
||||
if (name == nullptr || name[0] == '\0') {
|
||||
ipc::error("fail acquire: name is empty\n");
|
||||
return nullptr;
|
||||
}
|
||||
HANDLE h;
|
||||
auto fmt_name = ipc::detail::to_tchar(ipc::string{"__IPC_SHM__"} + name);
|
||||
// Opens a named file mapping object.
|
||||
if (mode == open) {
|
||||
h = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, fmt_name.c_str());
|
||||
}
|
||||
// Creates or opens a named file mapping object for a specified file.
|
||||
else {
|
||||
h = ::CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE | SEC_COMMIT,
|
||||
0, static_cast<DWORD>(size), fmt_name.c_str());
|
||||
// If the object exists before the function call, the function returns a handle to the existing object
|
||||
// (with its current size, not the specified size), and GetLastError returns ERROR_ALREADY_EXISTS.
|
||||
if ((mode == create) && (::GetLastError() == ERROR_ALREADY_EXISTS)) {
|
||||
::CloseHandle(h);
|
||||
h = NULL;
|
||||
}
|
||||
}
|
||||
if (h == NULL) {
|
||||
ipc::error("fail CreateFileMapping/OpenFileMapping[%d]: %s\n", static_cast<int>(::GetLastError()), name);
|
||||
return nullptr;
|
||||
}
|
||||
auto ii = mem::alloc<id_info_t>();
|
||||
ii->h_ = h;
|
||||
ii->size_ = size;
|
||||
return ii;
|
||||
}
|
||||
|
||||
void * get_mem(id_t id, std::size_t * size) {
|
||||
if (id == nullptr) {
|
||||
ipc::error("fail get_mem: invalid id (null)\n");
|
||||
return nullptr;
|
||||
}
|
||||
auto ii = static_cast<id_info_t*>(id);
|
||||
if (ii->mem_ != nullptr) {
|
||||
if (size != nullptr) *size = ii->size_;
|
||||
return ii->mem_;
|
||||
}
|
||||
if (ii->h_ == NULL) {
|
||||
ipc::error("fail to_mem: invalid id (h = null)\n");
|
||||
return nullptr;
|
||||
}
|
||||
LPVOID mem = ::MapViewOfFile(ii->h_, FILE_MAP_ALL_ACCESS, 0, 0, 0);
|
||||
if (mem == NULL) {
|
||||
ipc::error("fail MapViewOfFile[%d]\n", static_cast<int>(::GetLastError()));
|
||||
return nullptr;
|
||||
}
|
||||
MEMORY_BASIC_INFORMATION mem_info;
|
||||
if (::VirtualQuery(mem, &mem_info, sizeof(mem_info)) == 0) {
|
||||
ipc::error("fail VirtualQuery[%d]\n", static_cast<int>(::GetLastError()));
|
||||
return nullptr;
|
||||
}
|
||||
ii->mem_ = mem;
|
||||
ii->size_ = static_cast<std::size_t>(mem_info.RegionSize);
|
||||
if (size != nullptr) *size = ii->size_;
|
||||
return static_cast<void *>(mem);
|
||||
}
|
||||
|
||||
void release(id_t id) {
|
||||
if (id == nullptr) {
|
||||
ipc::error("fail release: invalid id (null)\n");
|
||||
return;
|
||||
}
|
||||
auto ii = static_cast<id_info_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 ::UnmapViewOfFile(static_cast<LPCVOID>(ii->mem_));
|
||||
if (ii->h_ == NULL) {
|
||||
ipc::error("fail release: invalid id (h = null)\n");
|
||||
}
|
||||
else ::CloseHandle(ii->h_);
|
||||
mem::free(ii);
|
||||
}
|
||||
|
||||
void remove(id_t id) {
|
||||
if (id == nullptr) {
|
||||
ipc::error("fail release: invalid id (null)\n");
|
||||
return;
|
||||
}
|
||||
release(id);
|
||||
}
|
||||
|
||||
void remove(char const * name) {
|
||||
if (name == nullptr || name[0] == '\0') {
|
||||
ipc::error("fail remove: name is empty\n");
|
||||
return;
|
||||
}
|
||||
// Do Nothing.
|
||||
}
|
||||
|
||||
} // namespace shm
|
||||
} // namespace ipc
|
||||
#include "shm.h"
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "def.h"
|
||||
#include "log.h"
|
||||
#include "pool_alloc.h"
|
||||
|
||||
#include "platform/to_tchar.h"
|
||||
#include "memory/resource.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct id_info_t {
|
||||
HANDLE h_ = NULL;
|
||||
void* mem_ = nullptr;
|
||||
std::size_t size_ = 0;
|
||||
};
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
namespace ipc {
|
||||
namespace shm {
|
||||
|
||||
id_t acquire(char const * name, std::size_t size, unsigned mode) {
|
||||
if (name == nullptr || name[0] == '\0') {
|
||||
ipc::error("fail acquire: name is empty\n");
|
||||
return nullptr;
|
||||
}
|
||||
HANDLE h;
|
||||
auto fmt_name = ipc::detail::to_tchar(ipc::string{"__IPC_SHM__"} + name);
|
||||
// Opens a named file mapping object.
|
||||
if (mode == open) {
|
||||
h = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, fmt_name.c_str());
|
||||
}
|
||||
// Creates or opens a named file mapping object for a specified file.
|
||||
else {
|
||||
h = ::CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE | SEC_COMMIT,
|
||||
0, static_cast<DWORD>(size), fmt_name.c_str());
|
||||
// If the object exists before the function call, the function returns a handle to the existing object
|
||||
// (with its current size, not the specified size), and GetLastError returns ERROR_ALREADY_EXISTS.
|
||||
if ((mode == create) && (::GetLastError() == ERROR_ALREADY_EXISTS)) {
|
||||
::CloseHandle(h);
|
||||
h = NULL;
|
||||
}
|
||||
}
|
||||
if (h == NULL) {
|
||||
ipc::error("fail CreateFileMapping/OpenFileMapping[%d]: %s\n", static_cast<int>(::GetLastError()), name);
|
||||
return nullptr;
|
||||
}
|
||||
auto ii = mem::alloc<id_info_t>();
|
||||
ii->h_ = h;
|
||||
ii->size_ = size;
|
||||
return ii;
|
||||
}
|
||||
|
||||
void * get_mem(id_t id, std::size_t * size) {
|
||||
if (id == nullptr) {
|
||||
ipc::error("fail get_mem: invalid id (null)\n");
|
||||
return nullptr;
|
||||
}
|
||||
auto ii = static_cast<id_info_t*>(id);
|
||||
if (ii->mem_ != nullptr) {
|
||||
if (size != nullptr) *size = ii->size_;
|
||||
return ii->mem_;
|
||||
}
|
||||
if (ii->h_ == NULL) {
|
||||
ipc::error("fail to_mem: invalid id (h = null)\n");
|
||||
return nullptr;
|
||||
}
|
||||
LPVOID mem = ::MapViewOfFile(ii->h_, FILE_MAP_ALL_ACCESS, 0, 0, 0);
|
||||
if (mem == NULL) {
|
||||
ipc::error("fail MapViewOfFile[%d]\n", static_cast<int>(::GetLastError()));
|
||||
return nullptr;
|
||||
}
|
||||
MEMORY_BASIC_INFORMATION mem_info;
|
||||
if (::VirtualQuery(mem, &mem_info, sizeof(mem_info)) == 0) {
|
||||
ipc::error("fail VirtualQuery[%d]\n", static_cast<int>(::GetLastError()));
|
||||
return nullptr;
|
||||
}
|
||||
ii->mem_ = mem;
|
||||
ii->size_ = static_cast<std::size_t>(mem_info.RegionSize);
|
||||
if (size != nullptr) *size = ii->size_;
|
||||
return static_cast<void *>(mem);
|
||||
}
|
||||
|
||||
void release(id_t id) {
|
||||
if (id == nullptr) {
|
||||
ipc::error("fail release: invalid id (null)\n");
|
||||
return;
|
||||
}
|
||||
auto ii = static_cast<id_info_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 ::UnmapViewOfFile(static_cast<LPCVOID>(ii->mem_));
|
||||
if (ii->h_ == NULL) {
|
||||
ipc::error("fail release: invalid id (h = null)\n");
|
||||
}
|
||||
else ::CloseHandle(ii->h_);
|
||||
mem::free(ii);
|
||||
}
|
||||
|
||||
void remove(id_t id) {
|
||||
if (id == nullptr) {
|
||||
ipc::error("fail release: invalid id (null)\n");
|
||||
return;
|
||||
}
|
||||
release(id);
|
||||
}
|
||||
|
||||
void remove(char const * name) {
|
||||
if (name == nullptr || name[0] == '\0') {
|
||||
ipc::error("fail remove: name is empty\n");
|
||||
return;
|
||||
}
|
||||
// Do Nothing.
|
||||
}
|
||||
|
||||
} // namespace shm
|
||||
} // namespace ipc
|
||||
|
||||
@ -1,29 +1,29 @@
|
||||
#include "tls_pointer.h"
|
||||
|
||||
#include <pthread.h> // pthread_...
|
||||
|
||||
namespace ipc {
|
||||
namespace tls {
|
||||
|
||||
key_t create(destructor_t destructor) {
|
||||
pthread_key_t k;
|
||||
if (pthread_key_create(&k, destructor) == 0) {
|
||||
return static_cast<key_t>(k);
|
||||
}
|
||||
return invalid_value;
|
||||
}
|
||||
|
||||
void release(key_t key) {
|
||||
pthread_key_delete(static_cast<pthread_key_t>(key));
|
||||
}
|
||||
|
||||
bool set(key_t key, void* ptr) {
|
||||
return pthread_setspecific(static_cast<pthread_key_t>(key), ptr) == 0;
|
||||
}
|
||||
|
||||
void* get(key_t key) {
|
||||
return pthread_getspecific(static_cast<pthread_key_t>(key));
|
||||
}
|
||||
|
||||
} // namespace tls
|
||||
} // namespace ipc
|
||||
#include "tls_pointer.h"
|
||||
|
||||
#include <pthread.h> // pthread_...
|
||||
|
||||
namespace ipc {
|
||||
namespace tls {
|
||||
|
||||
key_t create(destructor_t destructor) {
|
||||
pthread_key_t k;
|
||||
if (pthread_key_create(&k, destructor) == 0) {
|
||||
return static_cast<key_t>(k);
|
||||
}
|
||||
return invalid_value;
|
||||
}
|
||||
|
||||
void release(key_t key) {
|
||||
pthread_key_delete(static_cast<pthread_key_t>(key));
|
||||
}
|
||||
|
||||
bool set(key_t key, void* ptr) {
|
||||
return pthread_setspecific(static_cast<pthread_key_t>(key), ptr) == 0;
|
||||
}
|
||||
|
||||
void* get(key_t key) {
|
||||
return pthread_getspecific(static_cast<pthread_key_t>(key));
|
||||
}
|
||||
|
||||
} // namespace tls
|
||||
} // namespace ipc
|
||||
|
||||
@ -1,183 +1,230 @@
|
||||
#include "tls_pointer.h"
|
||||
|
||||
#include <Windows.h> // ::Tls...
|
||||
#include <unordered_map> // std::unordered_map
|
||||
|
||||
namespace ipc {
|
||||
|
||||
/*
|
||||
* <Remarks>
|
||||
*
|
||||
* Windows doesn't support a per-thread destructor with its TLS primitives.
|
||||
* So, here will build it manually by inserting a function to be called on each thread's exit.
|
||||
*
|
||||
* <Reference>
|
||||
* - https://www.codeproject.com/Articles/8113/Thread-Local-Storage-The-C-Way
|
||||
* - https://src.chromium.org/viewvc/chrome/trunk/src/base/threading/thread_local_storage_win.cc
|
||||
* - https://github.com/mirror/mingw-org-wsl/blob/master/src/libcrt/crt/tlssup.c
|
||||
* - https://github.com/Alexpux/mingw-w64/blob/master/mingw-w64-crt/crt/tlssup.c
|
||||
* - http://svn.boost.org/svn/boost/trunk/libs/thread/src/win32/tss_pe.cpp
|
||||
*/
|
||||
|
||||
namespace {
|
||||
|
||||
struct tls_data {
|
||||
using destructor_t = void(*)(void*);
|
||||
using map_t = std::unordered_map<tls::key_t, tls_data>;
|
||||
|
||||
static DWORD& key() {
|
||||
static DWORD rec_key = ::TlsAlloc();
|
||||
return rec_key;
|
||||
}
|
||||
|
||||
static map_t* records(map_t* rec) {
|
||||
::TlsSetValue(key(), static_cast<LPVOID>(rec));
|
||||
return rec;
|
||||
}
|
||||
|
||||
static map_t* records() {
|
||||
return static_cast<map_t*>(::TlsGetValue(key()));
|
||||
}
|
||||
|
||||
tls::key_t key_ = tls::invalid_value;
|
||||
destructor_t destructor_ = nullptr;
|
||||
|
||||
tls_data() = default;
|
||||
|
||||
tls_data(tls::key_t key, destructor_t destructor)
|
||||
: key_ (key)
|
||||
, destructor_(destructor)
|
||||
{}
|
||||
|
||||
tls_data(tls_data&& rhs) : tls_data() {
|
||||
(*this) = std::move(rhs);
|
||||
}
|
||||
|
||||
tls_data& operator=(tls_data&& rhs) {
|
||||
key_ = rhs.key_;
|
||||
destructor_ = rhs.destructor_;
|
||||
rhs.key_ = 0;
|
||||
rhs.destructor_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
~tls_data() {
|
||||
if (destructor_) destructor_(tls::get(key_));
|
||||
}
|
||||
};
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
namespace tls {
|
||||
|
||||
key_t create(destructor_t destructor) {
|
||||
key_t key = static_cast<key_t>(::TlsAlloc());
|
||||
if (key == TLS_OUT_OF_INDEXES) return invalid_value;
|
||||
auto rec = tls_data::records();
|
||||
if (rec == nullptr) rec = tls_data::records(new tls_data::map_t);
|
||||
if (rec == nullptr) return key;
|
||||
rec->emplace(key, tls_data{ key, destructor });
|
||||
return key;
|
||||
}
|
||||
|
||||
void release(key_t key) {
|
||||
auto rec = tls_data::records();
|
||||
if (rec == nullptr) return;
|
||||
rec->erase(key);
|
||||
::TlsFree(static_cast<DWORD>(key));
|
||||
}
|
||||
|
||||
bool set(key_t key, void* ptr) {
|
||||
return ::TlsSetValue(static_cast<DWORD>(key),
|
||||
static_cast<LPVOID>(ptr)) == TRUE;
|
||||
}
|
||||
|
||||
void* get(key_t key) {
|
||||
return static_cast<void*>(::TlsGetValue(static_cast<DWORD>(key)));
|
||||
}
|
||||
|
||||
} // namespace tls
|
||||
|
||||
namespace {
|
||||
|
||||
void OnThreadExit() {
|
||||
auto rec = tls_data::records();
|
||||
if (rec == nullptr) return;
|
||||
delete rec;
|
||||
tls_data::records(nullptr);
|
||||
}
|
||||
|
||||
void NTAPI OnTlsCallback(PVOID, DWORD dwReason, PVOID) {
|
||||
if (dwReason == DLL_THREAD_DETACH) OnThreadExit();
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Call destructors on thread exit
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
|
||||
#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__)
|
||||
|
||||
#pragma comment(linker, "/INCLUDE:_tls_used")
|
||||
#pragma comment(linker, "/INCLUDE:_tls_xl_b__")
|
||||
|
||||
extern "C" {
|
||||
# pragma const_seg(".CRT$XLB")
|
||||
extern const PIMAGE_TLS_CALLBACK _tls_xl_b__;
|
||||
const PIMAGE_TLS_CALLBACK _tls_xl_b__ = OnTlsCallback;
|
||||
# pragma const_seg()
|
||||
}
|
||||
|
||||
#else /*!WIN64*/
|
||||
|
||||
#pragma comment(linker, "/INCLUDE:__tls_used")
|
||||
#pragma comment(linker, "/INCLUDE:__tls_xl_b__")
|
||||
|
||||
extern "C" {
|
||||
# pragma data_seg(".CRT$XLB")
|
||||
PIMAGE_TLS_CALLBACK _tls_xl_b__ = OnTlsCallback;
|
||||
# pragma data_seg()
|
||||
}
|
||||
|
||||
#endif/*!WIN64*/
|
||||
|
||||
#elif defined(__GNUC__)
|
||||
|
||||
#define IPC_CRTALLOC__(x) __attribute__ ((section (x) ))
|
||||
|
||||
#if defined(__MINGW64__) || (__MINGW64_VERSION_MAJOR) || \
|
||||
(__MINGW32_MAJOR_VERSION > 3) || ((__MINGW32_MAJOR_VERSION == 3) && (__MINGW32_MINOR_VERSION >= 18))
|
||||
|
||||
extern "C" {
|
||||
IPC_CRTALLOC__(".CRT$XLB") PIMAGE_TLS_CALLBACK _tls_xl_b__ = OnTlsCallback;
|
||||
}
|
||||
|
||||
#else /*!MINGW*/
|
||||
|
||||
extern "C" {
|
||||
ULONG _tls_index__ = 0;
|
||||
|
||||
IPC_CRTALLOC__(".tls$AAA") char _tls_start__ = 0;
|
||||
IPC_CRTALLOC__(".tls$ZZZ") char _tls_end__ = 0;
|
||||
|
||||
IPC_CRTALLOC__(".CRT$XLA") PIMAGE_TLS_CALLBACK _tls_xl_a__ = 0;
|
||||
IPC_CRTALLOC__(".CRT$XLB") PIMAGE_TLS_CALLBACK _tls_xl_b__ = OnTlsCallback;
|
||||
IPC_CRTALLOC__(".CRT$XLZ") PIMAGE_TLS_CALLBACK _tls_xl_z__ = 0;
|
||||
}
|
||||
|
||||
extern "C" NX_CRTALLOC_(".tls") const IMAGE_TLS_DIRECTORY _tls_used = {
|
||||
(ULONG_PTR)(&_tls_start__ + 1),
|
||||
(ULONG_PTR) &_tls_end__,
|
||||
(ULONG_PTR) &_tls_index__,
|
||||
(ULONG_PTR) &_tls_xl_b__,
|
||||
(ULONG)0, (ULONG)0
|
||||
}
|
||||
|
||||
#endif/*!MINGW*/
|
||||
|
||||
#endif/*_MSC_VER, __GNUC__*/
|
||||
|
||||
} // namespace ipc
|
||||
#include "tls_pointer.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <Windows.h> // ::Tls...
|
||||
#include <atomic>
|
||||
#include <unordered_set> // std::unordered_set
|
||||
|
||||
namespace ipc {
|
||||
|
||||
/*
|
||||
* <Remarks>
|
||||
*
|
||||
* Windows doesn't support a per-thread destructor with its TLS primitives.
|
||||
* So, here will build it manually by inserting a function to be called on each thread's exit.
|
||||
*
|
||||
* <Reference>
|
||||
* - https://www.codeproject.com/Articles/8113/Thread-Local-Storage-The-C-Way
|
||||
* - https://src.chromium.org/viewvc/chrome/trunk/src/base/threading/thread_local_storage_win.cc
|
||||
* - https://github.com/mirror/mingw-org-wsl/blob/master/src/libcrt/crt/tlssup.c
|
||||
* - https://github.com/Alexpux/mingw-w64/blob/master/mingw-w64-crt/crt/tlssup.c
|
||||
* - http://svn.boost.org/svn/boost/trunk/libs/thread/src/win32/tss_pe.cpp
|
||||
*/
|
||||
|
||||
namespace {
|
||||
|
||||
struct tls_data {
|
||||
using destructor_t = void(*)(void*);
|
||||
|
||||
DWORD win_key_;
|
||||
destructor_t destructor_;
|
||||
|
||||
void destruct(void* data) {
|
||||
if ((destructor_ != nullptr) && (data != nullptr)) {
|
||||
destructor_(data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using rec_t = std::unordered_set<tls_data*>;
|
||||
|
||||
DWORD& record_key() {
|
||||
|
||||
struct key_gen {
|
||||
DWORD rec_key_;
|
||||
key_gen() : rec_key_(::TlsAlloc()) {
|
||||
if (rec_key_ == TLS_OUT_OF_INDEXES) {
|
||||
ipc::error("[record_key] TlsAlloc failed[%lu].\n", ::GetLastError());
|
||||
}
|
||||
}
|
||||
~key_gen() { ::TlsFree(rec_key_); }
|
||||
};
|
||||
|
||||
static key_gen gen;
|
||||
return gen.rec_key_;
|
||||
}
|
||||
|
||||
bool record(tls_data* tls) {
|
||||
auto rec = static_cast<rec_t*>(::TlsGetValue(record_key()));
|
||||
if (rec == nullptr) {
|
||||
if (FALSE == ::TlsSetValue(record_key(), static_cast<LPVOID>(rec = new rec_t))) {
|
||||
ipc::error("[record] TlsSetValue failed[%lu].\n", ::GetLastError());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
rec->insert(tls);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void erase_record(tls_data* tls) {
|
||||
auto rec = static_cast<rec_t*>(::TlsGetValue(record_key()));
|
||||
if (rec == nullptr) return;
|
||||
rec->erase(tls);
|
||||
}
|
||||
|
||||
static void clear_all_records() {
|
||||
auto rec = static_cast<rec_t*>(::TlsGetValue(record_key()));
|
||||
if (rec == nullptr) return;
|
||||
for (auto tls : *rec) {
|
||||
if (tls != nullptr) {
|
||||
tls->destruct(::TlsGetValue(tls->win_key_));
|
||||
}
|
||||
}
|
||||
delete rec;
|
||||
::TlsSetValue(record_key(), static_cast<LPVOID>(nullptr));
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
namespace tls {
|
||||
|
||||
key_t create(destructor_t destructor) {
|
||||
record_key(); // gen record-key
|
||||
auto tls_dat = new tls_data { ::TlsAlloc(), destructor };
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
if (tls_dat->win_key_ == TLS_OUT_OF_INDEXES) {
|
||||
ipc::error("[tls::create] TlsAlloc failed[%lu].\n", ::GetLastError());
|
||||
delete tls_dat;
|
||||
return invalid_value;
|
||||
}
|
||||
return reinterpret_cast<key_t>(tls_dat);
|
||||
}
|
||||
|
||||
void release(key_t tls_key) {
|
||||
if (tls_key == invalid_value) {
|
||||
ipc::error("[tls::release] tls_key is invalid_value.\n");
|
||||
return;
|
||||
}
|
||||
auto tls_dat = reinterpret_cast<tls_data*>(tls_key);
|
||||
if (tls_dat == nullptr) {
|
||||
ipc::error("[tls::release] tls_dat is nullptr.\n");
|
||||
return;
|
||||
}
|
||||
erase_record(tls_dat);
|
||||
::TlsFree(tls_dat->win_key_);
|
||||
delete tls_dat;
|
||||
}
|
||||
|
||||
bool set(key_t tls_key, void* ptr) {
|
||||
if (tls_key == invalid_value) {
|
||||
ipc::error("[tls::set] tls_key is invalid_value.\n");
|
||||
return false;
|
||||
}
|
||||
auto tls_dat = reinterpret_cast<tls_data*>(tls_key);
|
||||
if (tls_dat == nullptr) {
|
||||
ipc::error("[tls::set] tls_dat is nullptr.\n");
|
||||
return false;
|
||||
}
|
||||
if (FALSE == ::TlsSetValue(tls_dat->win_key_, static_cast<LPVOID>(ptr))) {
|
||||
ipc::error("[tls::set] TlsSetValue failed[%lu].\n", ::GetLastError());
|
||||
return false;
|
||||
}
|
||||
record(tls_dat);
|
||||
return true;
|
||||
}
|
||||
|
||||
void* get(key_t tls_key) {
|
||||
if (tls_key == invalid_value) {
|
||||
ipc::error("[tls::get] tls_key is invalid_value.\n");
|
||||
return nullptr;
|
||||
}
|
||||
auto tls_dat = reinterpret_cast<tls_data*>(tls_key);
|
||||
if (tls_dat == nullptr) {
|
||||
ipc::error("[tls::get] tls_dat is nullptr.\n");
|
||||
return nullptr;
|
||||
}
|
||||
return ::TlsGetValue(tls_dat->win_key_);
|
||||
}
|
||||
|
||||
} // namespace tls
|
||||
|
||||
namespace {
|
||||
|
||||
void OnThreadExit() {
|
||||
clear_all_records();
|
||||
}
|
||||
|
||||
void NTAPI OnTlsCallback(PVOID, DWORD dwReason, PVOID) {
|
||||
if (dwReason == DLL_THREAD_DETACH) OnThreadExit();
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Call destructors on thread exit
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
|
||||
#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__)
|
||||
|
||||
#pragma comment(linker, "/INCLUDE:_tls_used")
|
||||
#pragma comment(linker, "/INCLUDE:_tls_xl_b__")
|
||||
|
||||
extern "C" {
|
||||
# pragma const_seg(".CRT$XLB")
|
||||
extern const PIMAGE_TLS_CALLBACK _tls_xl_b__;
|
||||
const PIMAGE_TLS_CALLBACK _tls_xl_b__ = OnTlsCallback;
|
||||
# pragma const_seg()
|
||||
}
|
||||
|
||||
#else /*!WIN64*/
|
||||
|
||||
#pragma comment(linker, "/INCLUDE:__tls_used")
|
||||
#pragma comment(linker, "/INCLUDE:__tls_xl_b__")
|
||||
|
||||
extern "C" {
|
||||
# pragma data_seg(".CRT$XLB")
|
||||
PIMAGE_TLS_CALLBACK _tls_xl_b__ = OnTlsCallback;
|
||||
# pragma data_seg()
|
||||
}
|
||||
|
||||
#endif/*!WIN64*/
|
||||
|
||||
#elif defined(__GNUC__)
|
||||
|
||||
#define IPC_CRTALLOC__(x) __attribute__ ((section (x) ))
|
||||
|
||||
#if defined(__MINGW64__) || (__MINGW64_VERSION_MAJOR) || \
|
||||
(__MINGW32_MAJOR_VERSION > 3) || ((__MINGW32_MAJOR_VERSION == 3) && (__MINGW32_MINOR_VERSION >= 18))
|
||||
|
||||
extern "C" {
|
||||
IPC_CRTALLOC__(".CRT$XLB") PIMAGE_TLS_CALLBACK _tls_xl_b__ = OnTlsCallback;
|
||||
}
|
||||
|
||||
#else /*!MINGW*/
|
||||
|
||||
extern "C" {
|
||||
ULONG _tls_index__ = 0;
|
||||
|
||||
IPC_CRTALLOC__(".tls$AAA") char _tls_start__ = 0;
|
||||
IPC_CRTALLOC__(".tls$ZZZ") char _tls_end__ = 0;
|
||||
|
||||
IPC_CRTALLOC__(".CRT$XLA") PIMAGE_TLS_CALLBACK _tls_xl_a__ = 0;
|
||||
IPC_CRTALLOC__(".CRT$XLB") PIMAGE_TLS_CALLBACK _tls_xl_b__ = OnTlsCallback;
|
||||
IPC_CRTALLOC__(".CRT$XLZ") PIMAGE_TLS_CALLBACK _tls_xl_z__ = 0;
|
||||
}
|
||||
|
||||
extern "C" NX_CRTALLOC_(".tls") const IMAGE_TLS_DIRECTORY _tls_used = {
|
||||
(ULONG_PTR)(&_tls_start__ + 1),
|
||||
(ULONG_PTR) &_tls_end__,
|
||||
(ULONG_PTR) &_tls_index__,
|
||||
(ULONG_PTR) &_tls_xl_b__,
|
||||
(ULONG)0, (ULONG)0
|
||||
}
|
||||
|
||||
#endif/*!MINGW*/
|
||||
|
||||
#endif/*_MSC_VER, __GNUC__*/
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
@ -1,67 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <tchar.h>
|
||||
|
||||
#include <type_traits>
|
||||
#include <string>
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
#include <cstring>
|
||||
|
||||
#include "concept.h"
|
||||
|
||||
#include "platform/detail.h"
|
||||
#include "memory/resource.h"
|
||||
|
||||
namespace ipc::detail {
|
||||
|
||||
struct has_value_type_ {
|
||||
template <typename T> static std::true_type check(typename T::value_type *);
|
||||
template <typename T> static std::false_type check(...);
|
||||
};
|
||||
|
||||
template <typename T, typename U, typename = decltype(has_value_type_::check<U>(nullptr))>
|
||||
struct is_same_char : std::is_same<T, U> {};
|
||||
|
||||
template <typename T, typename U>
|
||||
struct is_same_char<T, U, std::true_type> : std::is_same<T, typename U::value_type> {};
|
||||
|
||||
template <typename T, typename S, typename R = S>
|
||||
using IsSameChar = ipc::require<is_same_char<T, S>::value, R>;
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// to_tchar implementation
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename T = TCHAR>
|
||||
constexpr auto to_tchar(ipc::string && str) -> IsSameChar<T, ipc::string, ipc::string &&> {
|
||||
return std::move(str);
|
||||
}
|
||||
|
||||
template <typename T = TCHAR>
|
||||
constexpr auto to_tchar(ipc::string && str) -> IsSameChar<T, ipc::wstring> {
|
||||
return std::wstring_convert<
|
||||
std::codecvt_utf8_utf16<wchar_t>,
|
||||
wchar_t,
|
||||
ipc::mem::allocator<wchar_t>,
|
||||
ipc::mem::allocator<char>
|
||||
>{}.from_bytes(std::move(str));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline auto to_tchar(T* dst, char const * src, std::size_t size) -> IsSameChar<T, char, void> {
|
||||
std::memcpy(dst, src, size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline auto to_tchar(T* dst, char const * src, std::size_t size) -> IsSameChar<T, wchar_t, void> {
|
||||
auto wstr = std::wstring_convert<
|
||||
std::codecvt_utf8_utf16<wchar_t>,
|
||||
wchar_t,
|
||||
ipc::mem::allocator<wchar_t>,
|
||||
ipc::mem::allocator<char>
|
||||
>{}.from_bytes(src, src + size);
|
||||
std::memcpy(dst, wstr.data(), (ipc::detail::min)(wstr.size(), size));
|
||||
}
|
||||
|
||||
} // namespace ipc::detail
|
||||
#pragma once
|
||||
|
||||
#include <tchar.h>
|
||||
|
||||
#include <type_traits>
|
||||
#include <string>
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
#include <cstring>
|
||||
|
||||
#include "concept.h"
|
||||
|
||||
#include "platform/detail.h"
|
||||
#include "memory/resource.h"
|
||||
|
||||
namespace ipc::detail {
|
||||
|
||||
struct has_value_type_ {
|
||||
template <typename T> static std::true_type check(typename T::value_type *);
|
||||
template <typename T> static std::false_type check(...);
|
||||
};
|
||||
|
||||
template <typename T, typename U, typename = decltype(has_value_type_::check<U>(nullptr))>
|
||||
struct is_same_char : std::is_same<T, U> {};
|
||||
|
||||
template <typename T, typename U>
|
||||
struct is_same_char<T, U, std::true_type> : std::is_same<T, typename U::value_type> {};
|
||||
|
||||
template <typename T, typename S, typename R = S>
|
||||
using IsSameChar = ipc::require<is_same_char<T, S>::value, R>;
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// to_tchar implementation
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename T = TCHAR>
|
||||
constexpr auto to_tchar(ipc::string && str) -> IsSameChar<T, ipc::string, ipc::string &&> {
|
||||
return std::move(str);
|
||||
}
|
||||
|
||||
template <typename T = TCHAR>
|
||||
constexpr auto to_tchar(ipc::string && str) -> IsSameChar<T, ipc::wstring> {
|
||||
return std::wstring_convert<
|
||||
std::codecvt_utf8_utf16<wchar_t>,
|
||||
wchar_t,
|
||||
ipc::mem::allocator<wchar_t>,
|
||||
ipc::mem::allocator<char>
|
||||
>{}.from_bytes(std::move(str));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline auto to_tchar(T* dst, char const * src, std::size_t size) -> IsSameChar<T, char, void> {
|
||||
std::memcpy(dst, src, size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline auto to_tchar(T* dst, char const * src, std::size_t size) -> IsSameChar<T, wchar_t, void> {
|
||||
auto wstr = std::wstring_convert<
|
||||
std::codecvt_utf8_utf16<wchar_t>,
|
||||
wchar_t,
|
||||
ipc::mem::allocator<wchar_t>,
|
||||
ipc::mem::allocator<char>
|
||||
>{}.from_bytes(src, src + size);
|
||||
std::memcpy(dst, wstr.data(), (ipc::detail::min)(wstr.size(), size));
|
||||
}
|
||||
|
||||
} // namespace ipc::detail
|
||||
|
||||
@ -1,347 +1,347 @@
|
||||
#pragma once
|
||||
|
||||
#include <pthread.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <semaphore.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <tuple>
|
||||
|
||||
#include "def.h"
|
||||
#include "log.h"
|
||||
|
||||
#include "platform/detail.h"
|
||||
#include "memory/resource.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
inline static void calc_wait_time(timespec& ts, std::size_t tm) {
|
||||
::clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_nsec += tm * 1000000; // nanoseconds
|
||||
ts.tv_sec += ts.tv_nsec / 1000000000;
|
||||
ts.tv_nsec %= 1000000000;
|
||||
}
|
||||
|
||||
#pragma push_macro("IPC_PTHREAD_FUNC_")
|
||||
#undef IPC_PTHREAD_FUNC_
|
||||
#define IPC_PTHREAD_FUNC_(CALL, ...) \
|
||||
int eno; \
|
||||
if ((eno = ::CALL(__VA_ARGS__)) != 0) { \
|
||||
ipc::error("fail " #CALL "[%d]\n", eno); \
|
||||
return false; \
|
||||
} \
|
||||
return true
|
||||
|
||||
class mutex {
|
||||
pthread_mutex_t mutex_ = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
public:
|
||||
pthread_mutex_t& native() {
|
||||
return mutex_;
|
||||
}
|
||||
|
||||
bool open() {
|
||||
int eno;
|
||||
// init mutex
|
||||
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_mutex_init(&mutex_, &mutex_attr)) != 0) {
|
||||
ipc::error("fail pthread_mutex_init[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool close() {
|
||||
IPC_PTHREAD_FUNC_(pthread_mutex_destroy, &mutex_);
|
||||
}
|
||||
|
||||
bool lock() {
|
||||
IPC_PTHREAD_FUNC_(pthread_mutex_lock, &mutex_);
|
||||
}
|
||||
|
||||
bool unlock() {
|
||||
IPC_PTHREAD_FUNC_(pthread_mutex_unlock, &mutex_);
|
||||
}
|
||||
};
|
||||
|
||||
class condition {
|
||||
pthread_cond_t cond_ = PTHREAD_COND_INITIALIZER;
|
||||
|
||||
public:
|
||||
bool open() {
|
||||
int eno;
|
||||
// init condition
|
||||
pthread_condattr_t cond_attr;
|
||||
if ((eno = ::pthread_condattr_init(&cond_attr)) != 0) {
|
||||
ipc::error("fail pthread_condattr_init[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
IPC_UNUSED_ auto guard_cond_attr = unique_ptr(&cond_attr, ::pthread_condattr_destroy);
|
||||
if ((eno = ::pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED)) != 0) {
|
||||
ipc::error("fail pthread_condattr_setpshared[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
if ((eno = ::pthread_cond_init(&cond_, &cond_attr)) != 0) {
|
||||
ipc::error("fail pthread_cond_init[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool close() {
|
||||
IPC_PTHREAD_FUNC_(pthread_cond_destroy, &cond_);
|
||||
}
|
||||
|
||||
bool wait(mutex& mtx, std::size_t tm = invalid_value) {
|
||||
switch (tm) {
|
||||
case 0:
|
||||
return true;
|
||||
case invalid_value:
|
||||
IPC_PTHREAD_FUNC_(pthread_cond_wait, &cond_, &mtx.native());
|
||||
default: {
|
||||
timespec ts;
|
||||
calc_wait_time(ts, tm);
|
||||
int eno;
|
||||
if ((eno = ::pthread_cond_timedwait(&cond_, &mtx.native(), &ts)) != 0) {
|
||||
if (eno != ETIMEDOUT) {
|
||||
ipc::error("fail pthread_cond_timedwait[%d]: tm = %zd, tv_sec = %ld, tv_nsec = %ld\n",
|
||||
eno, tm, ts.tv_sec, ts.tv_nsec);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool notify() {
|
||||
IPC_PTHREAD_FUNC_(pthread_cond_signal, &cond_);
|
||||
}
|
||||
|
||||
bool broadcast() {
|
||||
IPC_PTHREAD_FUNC_(pthread_cond_broadcast, &cond_);
|
||||
}
|
||||
};
|
||||
|
||||
#pragma pop_macro("IPC_PTHREAD_FUNC_")
|
||||
|
||||
class sem_helper {
|
||||
public:
|
||||
using handle_t = sem_t*;
|
||||
|
||||
constexpr static handle_t invalid() noexcept {
|
||||
return SEM_FAILED;
|
||||
}
|
||||
|
||||
static handle_t open(char const* name, long count) {
|
||||
handle_t sem = ::sem_open(name, O_CREAT, 0666, count);
|
||||
if (sem == SEM_FAILED) {
|
||||
ipc::error("fail sem_open[%d]: %s\n", errno, name);
|
||||
return invalid();
|
||||
}
|
||||
return sem;
|
||||
}
|
||||
|
||||
#pragma push_macro("IPC_SEMAPHORE_FUNC_")
|
||||
#undef IPC_SEMAPHORE_FUNC_
|
||||
#define IPC_SEMAPHORE_FUNC_(CALL, ...) \
|
||||
if (::CALL(__VA_ARGS__) != 0) { \
|
||||
ipc::error("fail " #CALL "[%d]\n", errno); \
|
||||
return false; \
|
||||
} \
|
||||
return true
|
||||
|
||||
static bool close(handle_t h) {
|
||||
if (h == invalid()) return false;
|
||||
IPC_SEMAPHORE_FUNC_(sem_close, h);
|
||||
}
|
||||
|
||||
static bool destroy(char const* name) {
|
||||
IPC_SEMAPHORE_FUNC_(sem_unlink, name);
|
||||
}
|
||||
|
||||
static bool post(handle_t h) {
|
||||
if (h == invalid()) return false;
|
||||
IPC_SEMAPHORE_FUNC_(sem_post, h);
|
||||
}
|
||||
|
||||
static bool wait(handle_t h, std::size_t tm = invalid_value) {
|
||||
if (h == invalid()) return false;
|
||||
switch (tm) {
|
||||
case 0:
|
||||
return true;
|
||||
case invalid_value:
|
||||
IPC_SEMAPHORE_FUNC_(sem_wait, h);
|
||||
default: {
|
||||
timespec ts;
|
||||
calc_wait_time(ts, tm);
|
||||
if (::sem_timedwait(h, &ts) != 0) {
|
||||
if (errno != ETIMEDOUT) {
|
||||
ipc::error("fail sem_timedwait[%d]: tm = %zd, tv_sec = %ld, tv_nsec = %ld\n",
|
||||
errno, tm, ts.tv_sec, ts.tv_nsec);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma pop_macro("IPC_SEMAPHORE_FUNC_")
|
||||
};
|
||||
|
||||
class waiter_helper {
|
||||
mutex lock_;
|
||||
|
||||
std::atomic<unsigned> waiting_ { 0 };
|
||||
long counter_ = 0;
|
||||
|
||||
public:
|
||||
using handle_t = std::tuple<ipc::string, sem_helper::handle_t, sem_helper::handle_t>;
|
||||
|
||||
static handle_t invalid() noexcept {
|
||||
return std::make_tuple(ipc::string{}, sem_helper::invalid(), sem_helper::invalid());
|
||||
}
|
||||
|
||||
handle_t open_h(ipc::string && name) {
|
||||
auto sem = sem_helper::open(("__WAITER_HELPER_SEM__" + name).c_str(), 0);
|
||||
if (sem == sem_helper::invalid()) {
|
||||
return invalid();
|
||||
}
|
||||
auto han = sem_helper::open(("__WAITER_HELPER_HAN__" + name).c_str(), 0);
|
||||
if (han == sem_helper::invalid()) {
|
||||
return invalid();
|
||||
}
|
||||
return std::make_tuple(std::move(name), sem, han);
|
||||
}
|
||||
|
||||
void release_h(handle_t const & h) {
|
||||
sem_helper::close(std::get<2>(h));
|
||||
sem_helper::close(std::get<1>(h));
|
||||
}
|
||||
|
||||
void close_h(handle_t const & h) {
|
||||
auto const & name = std::get<0>(h);
|
||||
sem_helper::destroy(("__WAITER_HELPER_HAN__" + name).c_str());
|
||||
sem_helper::destroy(("__WAITER_HELPER_SEM__" + name).c_str());
|
||||
}
|
||||
|
||||
bool open() {
|
||||
return lock_.open();
|
||||
}
|
||||
|
||||
void close() {
|
||||
lock_.close();
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool wait_if(handle_t const & h, F&& pred, std::size_t tm = invalid_value) {
|
||||
waiting_.fetch_add(1, std::memory_order_release);
|
||||
{
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
if (!std::forward<F>(pred)()) return true;
|
||||
++ counter_;
|
||||
}
|
||||
bool ret = sem_helper::wait(std::get<1>(h), tm);
|
||||
waiting_.fetch_sub(1, std::memory_order_release);
|
||||
ret = sem_helper::post(std::get<2>(h)) && ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool notify(handle_t const & h) {
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
if (waiting_.load(std::memory_order_relaxed) == 0) {
|
||||
return true;
|
||||
}
|
||||
bool ret = true;
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
if (counter_ > 0) {
|
||||
ret = sem_helper::post(std::get<1>(h));
|
||||
-- counter_;
|
||||
ret = ret && sem_helper::wait(std::get<2>(h), default_timeut);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool broadcast(handle_t const & h) {
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
if (waiting_.load(std::memory_order_relaxed) == 0) {
|
||||
return true;
|
||||
}
|
||||
bool ret = true;
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
if (counter_ > 0) {
|
||||
for (long i = 0; i < counter_; ++i) {
|
||||
ret = ret && sem_helper::post(std::get<1>(h));
|
||||
}
|
||||
do {
|
||||
-- counter_;
|
||||
ret = ret && sem_helper::wait(std::get<2>(h), default_timeut);
|
||||
} while (counter_ > 0);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
class waiter {
|
||||
waiter_helper helper_;
|
||||
std::atomic<unsigned> opened_ { 0 };
|
||||
|
||||
public:
|
||||
using handle_t = waiter_helper::handle_t;
|
||||
|
||||
static handle_t invalid() noexcept {
|
||||
return waiter_helper::invalid();
|
||||
}
|
||||
|
||||
handle_t open(char const * name) {
|
||||
if (name == nullptr || name[0] == '\0') {
|
||||
return invalid();
|
||||
}
|
||||
if ((opened_.fetch_add(1, std::memory_order_acq_rel) == 0) && !helper_.open()) {
|
||||
return invalid();
|
||||
}
|
||||
return helper_.open_h(name);
|
||||
}
|
||||
|
||||
void close(handle_t h) {
|
||||
if (h == invalid()) return;
|
||||
helper_.release_h(h);
|
||||
if (opened_.fetch_sub(1, std::memory_order_release) == 1) {
|
||||
helper_.close_h(h);
|
||||
helper_.close();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool wait_if(handle_t h, F&& pred, std::size_t tm = invalid_value) {
|
||||
if (h == invalid()) return false;
|
||||
return helper_.wait_if(h, std::forward<F>(pred), tm);
|
||||
}
|
||||
|
||||
void notify(handle_t h) {
|
||||
if (h == invalid()) return;
|
||||
helper_.notify(h);
|
||||
}
|
||||
|
||||
void broadcast(handle_t h) {
|
||||
if (h == invalid()) return;
|
||||
helper_.broadcast(h);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
#pragma once
|
||||
|
||||
#include <pthread.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <semaphore.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <tuple>
|
||||
|
||||
#include "def.h"
|
||||
#include "log.h"
|
||||
|
||||
#include "platform/detail.h"
|
||||
#include "memory/resource.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
inline static void calc_wait_time(timespec& ts, std::size_t tm) {
|
||||
::clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_nsec += tm * 1000000; // nanoseconds
|
||||
ts.tv_sec += ts.tv_nsec / 1000000000;
|
||||
ts.tv_nsec %= 1000000000;
|
||||
}
|
||||
|
||||
#pragma push_macro("IPC_PTHREAD_FUNC_")
|
||||
#undef IPC_PTHREAD_FUNC_
|
||||
#define IPC_PTHREAD_FUNC_(CALL, ...) \
|
||||
int eno; \
|
||||
if ((eno = ::CALL(__VA_ARGS__)) != 0) { \
|
||||
ipc::error("fail " #CALL "[%d]\n", eno); \
|
||||
return false; \
|
||||
} \
|
||||
return true
|
||||
|
||||
class mutex {
|
||||
pthread_mutex_t mutex_ = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
public:
|
||||
pthread_mutex_t& native() {
|
||||
return mutex_;
|
||||
}
|
||||
|
||||
bool open() {
|
||||
int eno;
|
||||
// init mutex
|
||||
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_mutex_init(&mutex_, &mutex_attr)) != 0) {
|
||||
ipc::error("fail pthread_mutex_init[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool close() {
|
||||
IPC_PTHREAD_FUNC_(pthread_mutex_destroy, &mutex_);
|
||||
}
|
||||
|
||||
bool lock() {
|
||||
IPC_PTHREAD_FUNC_(pthread_mutex_lock, &mutex_);
|
||||
}
|
||||
|
||||
bool unlock() {
|
||||
IPC_PTHREAD_FUNC_(pthread_mutex_unlock, &mutex_);
|
||||
}
|
||||
};
|
||||
|
||||
class condition {
|
||||
pthread_cond_t cond_ = PTHREAD_COND_INITIALIZER;
|
||||
|
||||
public:
|
||||
bool open() {
|
||||
int eno;
|
||||
// init condition
|
||||
pthread_condattr_t cond_attr;
|
||||
if ((eno = ::pthread_condattr_init(&cond_attr)) != 0) {
|
||||
ipc::error("fail pthread_condattr_init[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
IPC_UNUSED_ auto guard_cond_attr = unique_ptr(&cond_attr, ::pthread_condattr_destroy);
|
||||
if ((eno = ::pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED)) != 0) {
|
||||
ipc::error("fail pthread_condattr_setpshared[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
if ((eno = ::pthread_cond_init(&cond_, &cond_attr)) != 0) {
|
||||
ipc::error("fail pthread_cond_init[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool close() {
|
||||
IPC_PTHREAD_FUNC_(pthread_cond_destroy, &cond_);
|
||||
}
|
||||
|
||||
bool wait(mutex& mtx, std::size_t tm = invalid_value) {
|
||||
switch (tm) {
|
||||
case 0:
|
||||
return true;
|
||||
case invalid_value:
|
||||
IPC_PTHREAD_FUNC_(pthread_cond_wait, &cond_, &mtx.native());
|
||||
default: {
|
||||
timespec ts;
|
||||
calc_wait_time(ts, tm);
|
||||
int eno;
|
||||
if ((eno = ::pthread_cond_timedwait(&cond_, &mtx.native(), &ts)) != 0) {
|
||||
if (eno != ETIMEDOUT) {
|
||||
ipc::error("fail pthread_cond_timedwait[%d]: tm = %zd, tv_sec = %ld, tv_nsec = %ld\n",
|
||||
eno, tm, ts.tv_sec, ts.tv_nsec);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool notify() {
|
||||
IPC_PTHREAD_FUNC_(pthread_cond_signal, &cond_);
|
||||
}
|
||||
|
||||
bool broadcast() {
|
||||
IPC_PTHREAD_FUNC_(pthread_cond_broadcast, &cond_);
|
||||
}
|
||||
};
|
||||
|
||||
#pragma pop_macro("IPC_PTHREAD_FUNC_")
|
||||
|
||||
class sem_helper {
|
||||
public:
|
||||
using handle_t = sem_t*;
|
||||
|
||||
constexpr static handle_t invalid() noexcept {
|
||||
return SEM_FAILED;
|
||||
}
|
||||
|
||||
static handle_t open(char const* name, long count) {
|
||||
handle_t sem = ::sem_open(name, O_CREAT, 0666, count);
|
||||
if (sem == SEM_FAILED) {
|
||||
ipc::error("fail sem_open[%d]: %s\n", errno, name);
|
||||
return invalid();
|
||||
}
|
||||
return sem;
|
||||
}
|
||||
|
||||
#pragma push_macro("IPC_SEMAPHORE_FUNC_")
|
||||
#undef IPC_SEMAPHORE_FUNC_
|
||||
#define IPC_SEMAPHORE_FUNC_(CALL, ...) \
|
||||
if (::CALL(__VA_ARGS__) != 0) { \
|
||||
ipc::error("fail " #CALL "[%d]\n", errno); \
|
||||
return false; \
|
||||
} \
|
||||
return true
|
||||
|
||||
static bool close(handle_t h) {
|
||||
if (h == invalid()) return false;
|
||||
IPC_SEMAPHORE_FUNC_(sem_close, h);
|
||||
}
|
||||
|
||||
static bool destroy(char const* name) {
|
||||
IPC_SEMAPHORE_FUNC_(sem_unlink, name);
|
||||
}
|
||||
|
||||
static bool post(handle_t h) {
|
||||
if (h == invalid()) return false;
|
||||
IPC_SEMAPHORE_FUNC_(sem_post, h);
|
||||
}
|
||||
|
||||
static bool wait(handle_t h, std::size_t tm = invalid_value) {
|
||||
if (h == invalid()) return false;
|
||||
switch (tm) {
|
||||
case 0:
|
||||
return true;
|
||||
case invalid_value:
|
||||
IPC_SEMAPHORE_FUNC_(sem_wait, h);
|
||||
default: {
|
||||
timespec ts;
|
||||
calc_wait_time(ts, tm);
|
||||
if (::sem_timedwait(h, &ts) != 0) {
|
||||
if (errno != ETIMEDOUT) {
|
||||
ipc::error("fail sem_timedwait[%d]: tm = %zd, tv_sec = %ld, tv_nsec = %ld\n",
|
||||
errno, tm, ts.tv_sec, ts.tv_nsec);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma pop_macro("IPC_SEMAPHORE_FUNC_")
|
||||
};
|
||||
|
||||
class waiter_helper {
|
||||
mutex lock_;
|
||||
|
||||
std::atomic<unsigned> waiting_ { 0 };
|
||||
long counter_ = 0;
|
||||
|
||||
public:
|
||||
using handle_t = std::tuple<ipc::string, sem_helper::handle_t, sem_helper::handle_t>;
|
||||
|
||||
static handle_t invalid() noexcept {
|
||||
return std::make_tuple(ipc::string{}, sem_helper::invalid(), sem_helper::invalid());
|
||||
}
|
||||
|
||||
handle_t open_h(ipc::string && name) {
|
||||
auto sem = sem_helper::open(("__WAITER_HELPER_SEM__" + name).c_str(), 0);
|
||||
if (sem == sem_helper::invalid()) {
|
||||
return invalid();
|
||||
}
|
||||
auto han = sem_helper::open(("__WAITER_HELPER_HAN__" + name).c_str(), 0);
|
||||
if (han == sem_helper::invalid()) {
|
||||
return invalid();
|
||||
}
|
||||
return std::make_tuple(std::move(name), sem, han);
|
||||
}
|
||||
|
||||
void release_h(handle_t const & h) {
|
||||
sem_helper::close(std::get<2>(h));
|
||||
sem_helper::close(std::get<1>(h));
|
||||
}
|
||||
|
||||
void close_h(handle_t const & h) {
|
||||
auto const & name = std::get<0>(h);
|
||||
sem_helper::destroy(("__WAITER_HELPER_HAN__" + name).c_str());
|
||||
sem_helper::destroy(("__WAITER_HELPER_SEM__" + name).c_str());
|
||||
}
|
||||
|
||||
bool open() {
|
||||
return lock_.open();
|
||||
}
|
||||
|
||||
void close() {
|
||||
lock_.close();
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool wait_if(handle_t const & h, F&& pred, std::size_t tm = invalid_value) {
|
||||
waiting_.fetch_add(1, std::memory_order_release);
|
||||
{
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
if (!std::forward<F>(pred)()) return true;
|
||||
++ counter_;
|
||||
}
|
||||
bool ret = sem_helper::wait(std::get<1>(h), tm);
|
||||
waiting_.fetch_sub(1, std::memory_order_release);
|
||||
ret = sem_helper::post(std::get<2>(h)) && ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool notify(handle_t const & h) {
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
if (waiting_.load(std::memory_order_relaxed) == 0) {
|
||||
return true;
|
||||
}
|
||||
bool ret = true;
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
if (counter_ > 0) {
|
||||
ret = sem_helper::post(std::get<1>(h));
|
||||
-- counter_;
|
||||
ret = ret && sem_helper::wait(std::get<2>(h), default_timeut);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool broadcast(handle_t const & h) {
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
if (waiting_.load(std::memory_order_relaxed) == 0) {
|
||||
return true;
|
||||
}
|
||||
bool ret = true;
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
if (counter_ > 0) {
|
||||
for (long i = 0; i < counter_; ++i) {
|
||||
ret = ret && sem_helper::post(std::get<1>(h));
|
||||
}
|
||||
do {
|
||||
-- counter_;
|
||||
ret = ret && sem_helper::wait(std::get<2>(h), default_timeut);
|
||||
} while (counter_ > 0);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
class waiter {
|
||||
waiter_helper helper_;
|
||||
std::atomic<unsigned> opened_ { 0 };
|
||||
|
||||
public:
|
||||
using handle_t = waiter_helper::handle_t;
|
||||
|
||||
static handle_t invalid() noexcept {
|
||||
return waiter_helper::invalid();
|
||||
}
|
||||
|
||||
handle_t open(char const * name) {
|
||||
if (name == nullptr || name[0] == '\0') {
|
||||
return invalid();
|
||||
}
|
||||
if ((opened_.fetch_add(1, std::memory_order_acq_rel) == 0) && !helper_.open()) {
|
||||
return invalid();
|
||||
}
|
||||
return helper_.open_h(name);
|
||||
}
|
||||
|
||||
void close(handle_t h) {
|
||||
if (h == invalid()) return;
|
||||
helper_.release_h(h);
|
||||
if (opened_.fetch_sub(1, std::memory_order_release) == 1) {
|
||||
helper_.close_h(h);
|
||||
helper_.close();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool wait_if(handle_t h, F&& pred, std::size_t tm = invalid_value) {
|
||||
if (h == invalid()) return false;
|
||||
return helper_.wait_if(h, std::forward<F>(pred), tm);
|
||||
}
|
||||
|
||||
void notify(handle_t h) {
|
||||
if (h == invalid()) return;
|
||||
helper_.notify(h);
|
||||
}
|
||||
|
||||
void broadcast(handle_t h) {
|
||||
if (h == invalid()) return;
|
||||
helper_.broadcast(h);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
|
||||
@ -1,215 +1,215 @@
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <tuple>
|
||||
|
||||
#include "rw_lock.h"
|
||||
#include "pool_alloc.h"
|
||||
#include "log.h"
|
||||
#include "shm.h"
|
||||
|
||||
#include "platform/to_tchar.h"
|
||||
#include "platform/detail.h"
|
||||
#include "memory/resource.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
class semaphore {
|
||||
HANDLE h_ = NULL;
|
||||
|
||||
public:
|
||||
static void remove(char const * /*name*/) {}
|
||||
|
||||
bool open(ipc::string && name, long count = 0, long limit = LONG_MAX) {
|
||||
h_ = ::CreateSemaphore(NULL, count, limit, ipc::detail::to_tchar(std::move(name)).c_str());
|
||||
if (h_ == NULL) {
|
||||
ipc::error("fail CreateSemaphore[%lu]: %s\n", ::GetLastError(), name.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void close() {
|
||||
::CloseHandle(h_);
|
||||
}
|
||||
|
||||
bool wait(std::size_t tm = invalid_value) {
|
||||
DWORD ret, ms = (tm == invalid_value) ? INFINITE : static_cast<DWORD>(tm);
|
||||
switch ((ret = ::WaitForSingleObject(h_, ms))) {
|
||||
case WAIT_OBJECT_0:
|
||||
return true;
|
||||
case WAIT_ABANDONED:
|
||||
case WAIT_TIMEOUT:
|
||||
default:
|
||||
ipc::error("fail WaitForSingleObject[%lu]: 0x%08X\n", ::GetLastError(), ret);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool post(long count = 1) {
|
||||
if (::ReleaseSemaphore(h_, count, NULL)) {
|
||||
return true;
|
||||
}
|
||||
ipc::error("fail ReleaseSemaphore[%lu]\n", ::GetLastError());
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class mutex : public semaphore {
|
||||
using semaphore::wait;
|
||||
using semaphore::post;
|
||||
|
||||
public:
|
||||
bool open(ipc::string && name) {
|
||||
return semaphore::open(std::move(name), 1, 1);
|
||||
}
|
||||
|
||||
bool lock () { return semaphore::wait(); }
|
||||
bool unlock() { return semaphore::post(); }
|
||||
};
|
||||
|
||||
class condition {
|
||||
mutex lock_;
|
||||
semaphore sema_, handshake_;
|
||||
|
||||
std::atomic<unsigned> * waiting_ = nullptr;
|
||||
long * counter_ = nullptr;
|
||||
|
||||
public:
|
||||
friend bool operator==(condition const & c1, condition const & c2) {
|
||||
return (c1.waiting_ == c2.waiting_) && (c1.counter_ == c2.counter_);
|
||||
}
|
||||
|
||||
friend bool operator!=(condition const & c1, condition const & c2) {
|
||||
return !(c1 == c2);
|
||||
}
|
||||
|
||||
static void remove(char const * name) {
|
||||
semaphore::remove((ipc::string{ "__COND_HAN__" } + name).c_str());
|
||||
semaphore::remove((ipc::string{ "__COND_SEM__" } + name).c_str());
|
||||
mutex ::remove((ipc::string{ "__COND_MTX__" } + name).c_str());
|
||||
}
|
||||
|
||||
bool open(ipc::string const & name, std::atomic<unsigned> * waiting, long * counter) {
|
||||
if (lock_ .open("__COND_MTX__" + name) &&
|
||||
sema_ .open("__COND_SEM__" + name) &&
|
||||
handshake_.open("__COND_HAN__" + name)) {
|
||||
waiting_ = waiting;
|
||||
counter_ = counter;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void close() {
|
||||
handshake_.close();
|
||||
sema_ .close();
|
||||
lock_ .close();
|
||||
}
|
||||
|
||||
template <typename Mutex, typename F>
|
||||
bool wait_if(Mutex& mtx, F&& pred, std::size_t tm = invalid_value) {
|
||||
waiting_->fetch_add(1, std::memory_order_release);
|
||||
{
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
if (!std::forward<F>(pred)()) return true;
|
||||
++ *counter_;
|
||||
}
|
||||
mtx.unlock();
|
||||
bool ret = sema_.wait(tm);
|
||||
waiting_->fetch_sub(1, std::memory_order_release);
|
||||
ret = handshake_.post() && ret;
|
||||
mtx.lock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool notify() {
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
if (waiting_->load(std::memory_order_relaxed) == 0) {
|
||||
return true;
|
||||
}
|
||||
bool ret = true;
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
if (*counter_ > 0) {
|
||||
ret = sema_.post();
|
||||
-- *counter_;
|
||||
ret = ret && handshake_.wait(default_timeut);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool broadcast() {
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
if (waiting_->load(std::memory_order_relaxed) == 0) {
|
||||
return true;
|
||||
}
|
||||
bool ret = true;
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
if (*counter_ > 0) {
|
||||
ret = sema_.post(*counter_);
|
||||
do {
|
||||
-- *counter_;
|
||||
ret = ret && handshake_.wait(default_timeut);
|
||||
} while (*counter_ > 0);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
class waiter {
|
||||
|
||||
std::atomic<unsigned> waiting_ { 0 };
|
||||
long counter_ = 0;
|
||||
|
||||
public:
|
||||
using handle_t = condition;
|
||||
|
||||
static handle_t invalid() {
|
||||
return condition {};
|
||||
}
|
||||
|
||||
handle_t open(char const * name) {
|
||||
if (name == nullptr || name[0] == '\0') {
|
||||
return invalid();
|
||||
}
|
||||
condition cond;
|
||||
if (cond.open(name, &waiting_, &counter_)) {
|
||||
return cond;
|
||||
}
|
||||
return invalid();
|
||||
}
|
||||
|
||||
void close(handle_t& h) {
|
||||
if (h == invalid()) return;
|
||||
h.close();
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool wait_if(handle_t& h, F&& pred, std::size_t tm = invalid_value) {
|
||||
if (h == invalid()) return false;
|
||||
|
||||
class non_mutex {
|
||||
public:
|
||||
void lock () noexcept {}
|
||||
void unlock() noexcept {}
|
||||
} nm;
|
||||
|
||||
return h.wait_if(nm, std::forward<F>(pred), tm);
|
||||
}
|
||||
|
||||
void notify(handle_t& h) {
|
||||
if (h == invalid()) return;
|
||||
h.notify();
|
||||
}
|
||||
|
||||
void broadcast(handle_t& h) {
|
||||
if (h == invalid()) return;
|
||||
h.broadcast();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <tuple>
|
||||
|
||||
#include "rw_lock.h"
|
||||
#include "pool_alloc.h"
|
||||
#include "log.h"
|
||||
#include "shm.h"
|
||||
|
||||
#include "platform/to_tchar.h"
|
||||
#include "platform/detail.h"
|
||||
#include "memory/resource.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
class semaphore {
|
||||
HANDLE h_ = NULL;
|
||||
|
||||
public:
|
||||
static void remove(char const * /*name*/) {}
|
||||
|
||||
bool open(ipc::string && name, long count = 0, long limit = LONG_MAX) {
|
||||
h_ = ::CreateSemaphore(NULL, count, limit, ipc::detail::to_tchar(std::move(name)).c_str());
|
||||
if (h_ == NULL) {
|
||||
ipc::error("fail CreateSemaphore[%lu]: %s\n", ::GetLastError(), name.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void close() {
|
||||
::CloseHandle(h_);
|
||||
}
|
||||
|
||||
bool wait(std::size_t tm = invalid_value) {
|
||||
DWORD ret, ms = (tm == invalid_value) ? INFINITE : static_cast<DWORD>(tm);
|
||||
switch ((ret = ::WaitForSingleObject(h_, ms))) {
|
||||
case WAIT_OBJECT_0:
|
||||
return true;
|
||||
case WAIT_ABANDONED:
|
||||
case WAIT_TIMEOUT:
|
||||
default:
|
||||
ipc::error("fail WaitForSingleObject[%lu]: 0x%08X\n", ::GetLastError(), ret);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool post(long count = 1) {
|
||||
if (::ReleaseSemaphore(h_, count, NULL)) {
|
||||
return true;
|
||||
}
|
||||
ipc::error("fail ReleaseSemaphore[%lu]\n", ::GetLastError());
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class mutex : public semaphore {
|
||||
using semaphore::wait;
|
||||
using semaphore::post;
|
||||
|
||||
public:
|
||||
bool open(ipc::string && name) {
|
||||
return semaphore::open(std::move(name), 1, 1);
|
||||
}
|
||||
|
||||
bool lock () { return semaphore::wait(); }
|
||||
bool unlock() { return semaphore::post(); }
|
||||
};
|
||||
|
||||
class condition {
|
||||
mutex lock_;
|
||||
semaphore sema_, handshake_;
|
||||
|
||||
std::atomic<unsigned> * waiting_ = nullptr;
|
||||
long * counter_ = nullptr;
|
||||
|
||||
public:
|
||||
friend bool operator==(condition const & c1, condition const & c2) {
|
||||
return (c1.waiting_ == c2.waiting_) && (c1.counter_ == c2.counter_);
|
||||
}
|
||||
|
||||
friend bool operator!=(condition const & c1, condition const & c2) {
|
||||
return !(c1 == c2);
|
||||
}
|
||||
|
||||
static void remove(char const * name) {
|
||||
semaphore::remove((ipc::string{ "__COND_HAN__" } + name).c_str());
|
||||
semaphore::remove((ipc::string{ "__COND_SEM__" } + name).c_str());
|
||||
mutex ::remove((ipc::string{ "__COND_MTX__" } + name).c_str());
|
||||
}
|
||||
|
||||
bool open(ipc::string const & name, std::atomic<unsigned> * waiting, long * counter) {
|
||||
if (lock_ .open("__COND_MTX__" + name) &&
|
||||
sema_ .open("__COND_SEM__" + name) &&
|
||||
handshake_.open("__COND_HAN__" + name)) {
|
||||
waiting_ = waiting;
|
||||
counter_ = counter;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void close() {
|
||||
handshake_.close();
|
||||
sema_ .close();
|
||||
lock_ .close();
|
||||
}
|
||||
|
||||
template <typename Mutex, typename F>
|
||||
bool wait_if(Mutex& mtx, F&& pred, std::size_t tm = invalid_value) {
|
||||
waiting_->fetch_add(1, std::memory_order_release);
|
||||
{
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
if (!std::forward<F>(pred)()) return true;
|
||||
++ *counter_;
|
||||
}
|
||||
mtx.unlock();
|
||||
bool ret = sema_.wait(tm);
|
||||
waiting_->fetch_sub(1, std::memory_order_release);
|
||||
ret = handshake_.post() && ret;
|
||||
mtx.lock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool notify() {
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
if (waiting_->load(std::memory_order_relaxed) == 0) {
|
||||
return true;
|
||||
}
|
||||
bool ret = true;
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
if (*counter_ > 0) {
|
||||
ret = sema_.post();
|
||||
-- *counter_;
|
||||
ret = ret && handshake_.wait(default_timeut);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool broadcast() {
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
if (waiting_->load(std::memory_order_relaxed) == 0) {
|
||||
return true;
|
||||
}
|
||||
bool ret = true;
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
if (*counter_ > 0) {
|
||||
ret = sema_.post(*counter_);
|
||||
do {
|
||||
-- *counter_;
|
||||
ret = ret && handshake_.wait(default_timeut);
|
||||
} while (*counter_ > 0);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
class waiter {
|
||||
|
||||
std::atomic<unsigned> waiting_ { 0 };
|
||||
long counter_ = 0;
|
||||
|
||||
public:
|
||||
using handle_t = condition;
|
||||
|
||||
static handle_t invalid() {
|
||||
return condition {};
|
||||
}
|
||||
|
||||
handle_t open(char const * name) {
|
||||
if (name == nullptr || name[0] == '\0') {
|
||||
return invalid();
|
||||
}
|
||||
condition cond;
|
||||
if (cond.open(name, &waiting_, &counter_)) {
|
||||
return cond;
|
||||
}
|
||||
return invalid();
|
||||
}
|
||||
|
||||
void close(handle_t& h) {
|
||||
if (h == invalid()) return;
|
||||
h.close();
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool wait_if(handle_t& h, F&& pred, std::size_t tm = invalid_value) {
|
||||
if (h == invalid()) return false;
|
||||
|
||||
class non_mutex {
|
||||
public:
|
||||
void lock () noexcept {}
|
||||
void unlock() noexcept {}
|
||||
} nm;
|
||||
|
||||
return h.wait_if(nm, std::forward<F>(pred), tm);
|
||||
}
|
||||
|
||||
void notify(handle_t& h) {
|
||||
if (h == invalid()) return;
|
||||
h.notify();
|
||||
}
|
||||
|
||||
void broadcast(handle_t& h) {
|
||||
if (h == invalid()) return;
|
||||
h.broadcast();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
|
||||
@ -1,286 +1,286 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <atomic>
|
||||
#include <utility>
|
||||
|
||||
#include "shm.h"
|
||||
|
||||
#include "memory/resource.h"
|
||||
#include "platform/detail.h"
|
||||
#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || \
|
||||
defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || \
|
||||
defined(WINCE) || defined(_WIN32_WCE)
|
||||
|
||||
#include "platform/waiter_win.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
using mutex_impl = ipc::detail::mutex;
|
||||
using semaphore_impl = ipc::detail::semaphore;
|
||||
|
||||
class condition_impl : public ipc::detail::condition {
|
||||
|
||||
ipc::shm::handle wait_h_, cnt_h_;
|
||||
|
||||
public:
|
||||
static void remove(char const * name) {
|
||||
ipc::detail::condition::remove(name);
|
||||
ipc::string n = name;
|
||||
ipc::shm::remove((n + "__COND_CNT__" ).c_str());
|
||||
ipc::shm::remove((n + "__COND_WAIT__").c_str());
|
||||
}
|
||||
|
||||
bool open(ipc::string const & name) {
|
||||
if (wait_h_.acquire((name + "__COND_WAIT__").c_str(), sizeof(std::atomic<unsigned>)) &&
|
||||
cnt_h_ .acquire((name + "__COND_CNT__" ).c_str(), sizeof(long))) {
|
||||
return ipc::detail::condition::open(name,
|
||||
static_cast<std::atomic<unsigned> *>(wait_h_.get()),
|
||||
static_cast<long *>(cnt_h_.get()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void close() {
|
||||
ipc::detail::condition::close();
|
||||
cnt_h_ .release();
|
||||
wait_h_.release();
|
||||
}
|
||||
|
||||
bool wait(mutex_impl& mtx, std::size_t tm = invalid_value) {
|
||||
return ipc::detail::condition::wait_if(mtx, [] { return true; }, tm);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
|
||||
#else /*!WIN*/
|
||||
|
||||
#include "platform/waiter_linux.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
class object_impl {
|
||||
ipc::shm::handle h_;
|
||||
|
||||
struct info_t {
|
||||
T object_;
|
||||
std::atomic<unsigned> opened_;
|
||||
};
|
||||
|
||||
public:
|
||||
static void remove(char const * name) {
|
||||
{
|
||||
ipc::shm::handle h { name, sizeof(info_t) };
|
||||
if (h.valid()) {
|
||||
auto info = static_cast<info_t*>(h.get());
|
||||
info->object_.close();
|
||||
}
|
||||
}
|
||||
ipc::shm::remove(name);
|
||||
}
|
||||
|
||||
T& object() {
|
||||
return static_cast<info_t*>(h_.get())->object_;
|
||||
}
|
||||
|
||||
template <typename... P>
|
||||
bool open(char const * name, P&&... params) {
|
||||
if (!h_.acquire(name, sizeof(info_t))) {
|
||||
return false;
|
||||
}
|
||||
auto info = static_cast<info_t*>(h_.get());
|
||||
if ((info->opened_.fetch_add(1, std::memory_order_acq_rel) == 0) &&
|
||||
!info->object_.open(std::forward<P>(params)...)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (!h_.valid()) return;
|
||||
auto info = static_cast<info_t*>(h_.get());
|
||||
if (info->opened_.fetch_sub(1, std::memory_order_release) == 1) {
|
||||
info->object_.close();
|
||||
}
|
||||
h_.release();
|
||||
}
|
||||
};
|
||||
|
||||
class mutex_impl : public object_impl<ipc::detail::mutex> {
|
||||
public:
|
||||
bool lock () { return object().lock (); }
|
||||
bool unlock() { return object().unlock(); }
|
||||
};
|
||||
|
||||
class condition_impl : public object_impl<ipc::detail::condition> {
|
||||
public:
|
||||
bool wait(mutex_impl& mtx, std::size_t tm = invalid_value) {
|
||||
return object().wait(mtx.object(), tm);
|
||||
}
|
||||
|
||||
bool notify () { return object().notify (); }
|
||||
bool broadcast() { return object().broadcast(); }
|
||||
};
|
||||
|
||||
class semaphore_impl {
|
||||
sem_helper::handle_t h_;
|
||||
ipc::shm::handle opened_; // std::atomic<unsigned>
|
||||
ipc::string name_;
|
||||
|
||||
auto cnt() {
|
||||
return static_cast<std::atomic<unsigned>*>(opened_.get());
|
||||
}
|
||||
|
||||
public:
|
||||
static void remove(char const * name) {
|
||||
sem_helper::destroy((ipc::string{ "__SEMAPHORE_IMPL_SEM__" } + name).c_str());
|
||||
ipc::shm::remove ((ipc::string{ "__SEMAPHORE_IMPL_CNT__" } + name).c_str());
|
||||
}
|
||||
|
||||
bool open(char const * name, long count) {
|
||||
name_ = name;
|
||||
if (!opened_.acquire(("__SEMAPHORE_IMPL_CNT__" + name_).c_str(), sizeof(std::atomic<unsigned>))) {
|
||||
return false;
|
||||
}
|
||||
if ((h_ = sem_helper::open(("__SEMAPHORE_IMPL_SEM__" + name_).c_str(), count)) == sem_helper::invalid()) {
|
||||
return false;
|
||||
}
|
||||
cnt()->fetch_add(1, std::memory_order_acq_rel);
|
||||
return true;
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (h_ == sem_helper::invalid()) return;
|
||||
sem_helper::close(h_);
|
||||
if (cnt() == nullptr) return;
|
||||
if (cnt()->fetch_sub(1, std::memory_order_release) == 1) {
|
||||
sem_helper::destroy(("__SEMAPHORE_IMPL_SEM__" + name_).c_str());
|
||||
}
|
||||
opened_.release();
|
||||
}
|
||||
|
||||
bool wait(std::size_t tm = invalid_value) {
|
||||
if (h_ == sem_helper::invalid()) return false;
|
||||
return sem_helper::wait(h_, tm);
|
||||
}
|
||||
|
||||
bool post(long count) {
|
||||
if (h_ == sem_helper::invalid()) return false;
|
||||
bool ret = true;
|
||||
for (long i = 0; i < count; ++i) {
|
||||
ret = ret && sem_helper::post(h_);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
|
||||
#endif/*!WIN*/
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
class waiter_wrapper {
|
||||
public:
|
||||
using waiter_t = detail::waiter;
|
||||
|
||||
private:
|
||||
waiter_t* w_ = nullptr;
|
||||
waiter_t::handle_t h_ = waiter_t::invalid();
|
||||
|
||||
public:
|
||||
waiter_wrapper() = default;
|
||||
explicit waiter_wrapper(waiter_t* w) {
|
||||
attach(w);
|
||||
}
|
||||
waiter_wrapper(const waiter_wrapper&) = delete;
|
||||
waiter_wrapper& operator=(const waiter_wrapper&) = delete;
|
||||
|
||||
waiter_t * waiter() { return w_; }
|
||||
waiter_t const * waiter() const { return w_; }
|
||||
|
||||
void attach(waiter_t* w) {
|
||||
close();
|
||||
w_ = w;
|
||||
}
|
||||
|
||||
bool valid() const {
|
||||
return (w_ != nullptr) && (h_ != waiter_t::invalid());
|
||||
}
|
||||
|
||||
bool open(char const * name) {
|
||||
if (w_ == nullptr) return false;
|
||||
close();
|
||||
h_ = w_->open(name);
|
||||
return valid();
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (!valid()) return;
|
||||
w_->close(h_);
|
||||
h_ = waiter_t::invalid();
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool wait_if(F&& pred, std::size_t tm = invalid_value) {
|
||||
if (!valid()) return false;
|
||||
return w_->wait_if(h_, std::forward<F>(pred), tm);
|
||||
}
|
||||
|
||||
bool notify() {
|
||||
if (!valid()) return false;
|
||||
w_->notify(h_);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool broadcast() {
|
||||
if (!valid()) return false;
|
||||
w_->broadcast(h_);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
class waiter : public detail::waiter_wrapper {
|
||||
|
||||
shm::handle shm_;
|
||||
|
||||
using detail::waiter_wrapper::attach;
|
||||
|
||||
public:
|
||||
waiter() = default;
|
||||
waiter(char const * name) {
|
||||
open(name);
|
||||
}
|
||||
|
||||
~waiter() {
|
||||
close();
|
||||
}
|
||||
|
||||
bool open(char const * name) {
|
||||
if (name == nullptr || name[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
close();
|
||||
if (!shm_.acquire((ipc::string{ "__SHM_WAITER__" } + name).c_str(), sizeof(waiter_t))) {
|
||||
return false;
|
||||
}
|
||||
attach(static_cast<waiter_t*>(shm_.get()));
|
||||
return detail::waiter_wrapper::open((ipc::string{ "__IMP_WAITER__" } + name).c_str());
|
||||
}
|
||||
|
||||
void close() {
|
||||
detail::waiter_wrapper::close();
|
||||
shm_.release();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <atomic>
|
||||
#include <utility>
|
||||
|
||||
#include "shm.h"
|
||||
|
||||
#include "memory/resource.h"
|
||||
#include "platform/detail.h"
|
||||
#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || \
|
||||
defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || \
|
||||
defined(WINCE) || defined(_WIN32_WCE)
|
||||
|
||||
#include "platform/waiter_win.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
using mutex_impl = ipc::detail::mutex;
|
||||
using semaphore_impl = ipc::detail::semaphore;
|
||||
|
||||
class condition_impl : public ipc::detail::condition {
|
||||
|
||||
ipc::shm::handle wait_h_, cnt_h_;
|
||||
|
||||
public:
|
||||
static void remove(char const * name) {
|
||||
ipc::detail::condition::remove(name);
|
||||
ipc::string n = name;
|
||||
ipc::shm::remove((n + "__COND_CNT__" ).c_str());
|
||||
ipc::shm::remove((n + "__COND_WAIT__").c_str());
|
||||
}
|
||||
|
||||
bool open(ipc::string const & name) {
|
||||
if (wait_h_.acquire((name + "__COND_WAIT__").c_str(), sizeof(std::atomic<unsigned>)) &&
|
||||
cnt_h_ .acquire((name + "__COND_CNT__" ).c_str(), sizeof(long))) {
|
||||
return ipc::detail::condition::open(name,
|
||||
static_cast<std::atomic<unsigned> *>(wait_h_.get()),
|
||||
static_cast<long *>(cnt_h_.get()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void close() {
|
||||
ipc::detail::condition::close();
|
||||
cnt_h_ .release();
|
||||
wait_h_.release();
|
||||
}
|
||||
|
||||
bool wait(mutex_impl& mtx, std::size_t tm = invalid_value) {
|
||||
return ipc::detail::condition::wait_if(mtx, [] { return true; }, tm);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
|
||||
#else /*!WIN*/
|
||||
|
||||
#include "platform/waiter_linux.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
class object_impl {
|
||||
ipc::shm::handle h_;
|
||||
|
||||
struct info_t {
|
||||
T object_;
|
||||
std::atomic<unsigned> opened_;
|
||||
};
|
||||
|
||||
public:
|
||||
static void remove(char const * name) {
|
||||
{
|
||||
ipc::shm::handle h { name, sizeof(info_t) };
|
||||
if (h.valid()) {
|
||||
auto info = static_cast<info_t*>(h.get());
|
||||
info->object_.close();
|
||||
}
|
||||
}
|
||||
ipc::shm::remove(name);
|
||||
}
|
||||
|
||||
T& object() {
|
||||
return static_cast<info_t*>(h_.get())->object_;
|
||||
}
|
||||
|
||||
template <typename... P>
|
||||
bool open(char const * name, P&&... params) {
|
||||
if (!h_.acquire(name, sizeof(info_t))) {
|
||||
return false;
|
||||
}
|
||||
auto info = static_cast<info_t*>(h_.get());
|
||||
if ((info->opened_.fetch_add(1, std::memory_order_acq_rel) == 0) &&
|
||||
!info->object_.open(std::forward<P>(params)...)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (!h_.valid()) return;
|
||||
auto info = static_cast<info_t*>(h_.get());
|
||||
if (info->opened_.fetch_sub(1, std::memory_order_release) == 1) {
|
||||
info->object_.close();
|
||||
}
|
||||
h_.release();
|
||||
}
|
||||
};
|
||||
|
||||
class mutex_impl : public object_impl<ipc::detail::mutex> {
|
||||
public:
|
||||
bool lock () { return object().lock (); }
|
||||
bool unlock() { return object().unlock(); }
|
||||
};
|
||||
|
||||
class condition_impl : public object_impl<ipc::detail::condition> {
|
||||
public:
|
||||
bool wait(mutex_impl& mtx, std::size_t tm = invalid_value) {
|
||||
return object().wait(mtx.object(), tm);
|
||||
}
|
||||
|
||||
bool notify () { return object().notify (); }
|
||||
bool broadcast() { return object().broadcast(); }
|
||||
};
|
||||
|
||||
class semaphore_impl {
|
||||
sem_helper::handle_t h_;
|
||||
ipc::shm::handle opened_; // std::atomic<unsigned>
|
||||
ipc::string name_;
|
||||
|
||||
auto cnt() {
|
||||
return static_cast<std::atomic<unsigned>*>(opened_.get());
|
||||
}
|
||||
|
||||
public:
|
||||
static void remove(char const * name) {
|
||||
sem_helper::destroy((ipc::string{ "__SEMAPHORE_IMPL_SEM__" } + name).c_str());
|
||||
ipc::shm::remove ((ipc::string{ "__SEMAPHORE_IMPL_CNT__" } + name).c_str());
|
||||
}
|
||||
|
||||
bool open(char const * name, long count) {
|
||||
name_ = name;
|
||||
if (!opened_.acquire(("__SEMAPHORE_IMPL_CNT__" + name_).c_str(), sizeof(std::atomic<unsigned>))) {
|
||||
return false;
|
||||
}
|
||||
if ((h_ = sem_helper::open(("__SEMAPHORE_IMPL_SEM__" + name_).c_str(), count)) == sem_helper::invalid()) {
|
||||
return false;
|
||||
}
|
||||
cnt()->fetch_add(1, std::memory_order_acq_rel);
|
||||
return true;
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (h_ == sem_helper::invalid()) return;
|
||||
sem_helper::close(h_);
|
||||
if (cnt() == nullptr) return;
|
||||
if (cnt()->fetch_sub(1, std::memory_order_release) == 1) {
|
||||
sem_helper::destroy(("__SEMAPHORE_IMPL_SEM__" + name_).c_str());
|
||||
}
|
||||
opened_.release();
|
||||
}
|
||||
|
||||
bool wait(std::size_t tm = invalid_value) {
|
||||
if (h_ == sem_helper::invalid()) return false;
|
||||
return sem_helper::wait(h_, tm);
|
||||
}
|
||||
|
||||
bool post(long count) {
|
||||
if (h_ == sem_helper::invalid()) return false;
|
||||
bool ret = true;
|
||||
for (long i = 0; i < count; ++i) {
|
||||
ret = ret && sem_helper::post(h_);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
|
||||
#endif/*!WIN*/
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
class waiter_wrapper {
|
||||
public:
|
||||
using waiter_t = detail::waiter;
|
||||
|
||||
private:
|
||||
waiter_t* w_ = nullptr;
|
||||
waiter_t::handle_t h_ = waiter_t::invalid();
|
||||
|
||||
public:
|
||||
waiter_wrapper() = default;
|
||||
explicit waiter_wrapper(waiter_t* w) {
|
||||
attach(w);
|
||||
}
|
||||
waiter_wrapper(const waiter_wrapper&) = delete;
|
||||
waiter_wrapper& operator=(const waiter_wrapper&) = delete;
|
||||
|
||||
waiter_t * waiter() { return w_; }
|
||||
waiter_t const * waiter() const { return w_; }
|
||||
|
||||
void attach(waiter_t* w) {
|
||||
close();
|
||||
w_ = w;
|
||||
}
|
||||
|
||||
bool valid() const {
|
||||
return (w_ != nullptr) && (h_ != waiter_t::invalid());
|
||||
}
|
||||
|
||||
bool open(char const * name) {
|
||||
if (w_ == nullptr) return false;
|
||||
close();
|
||||
h_ = w_->open(name);
|
||||
return valid();
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (!valid()) return;
|
||||
w_->close(h_);
|
||||
h_ = waiter_t::invalid();
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool wait_if(F&& pred, std::size_t tm = invalid_value) {
|
||||
if (!valid()) return false;
|
||||
return w_->wait_if(h_, std::forward<F>(pred), tm);
|
||||
}
|
||||
|
||||
bool notify() {
|
||||
if (!valid()) return false;
|
||||
w_->notify(h_);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool broadcast() {
|
||||
if (!valid()) return false;
|
||||
w_->broadcast(h_);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
class waiter : public detail::waiter_wrapper {
|
||||
|
||||
shm::handle shm_;
|
||||
|
||||
using detail::waiter_wrapper::attach;
|
||||
|
||||
public:
|
||||
waiter() = default;
|
||||
waiter(char const * name) {
|
||||
open(name);
|
||||
}
|
||||
|
||||
~waiter() {
|
||||
close();
|
||||
}
|
||||
|
||||
bool open(char const * name) {
|
||||
if (name == nullptr || name[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
close();
|
||||
if (!shm_.acquire((ipc::string{ "__SHM_WAITER__" } + name).c_str(), sizeof(waiter_t))) {
|
||||
return false;
|
||||
}
|
||||
attach(static_cast<waiter_t*>(shm_.get()));
|
||||
return detail::waiter_wrapper::open((ipc::string{ "__IMP_WAITER__" } + name).c_str());
|
||||
}
|
||||
|
||||
void close() {
|
||||
detail::waiter_wrapper::close();
|
||||
shm_.release();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
46
src/policy.h
46
src/policy.h
@ -1,23 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "def.h"
|
||||
#include "prod_cons.h"
|
||||
|
||||
#include "circ/elem_array.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace policy {
|
||||
|
||||
template <template <typename, std::size_t...> class Elems, typename Flag>
|
||||
struct choose;
|
||||
|
||||
template <typename Flag>
|
||||
struct choose<circ::elem_array, Flag> {
|
||||
template <std::size_t DataSize, std::size_t AlignSize>
|
||||
using elems_t = circ::elem_array<ipc::prod_cons_impl<Flag>, DataSize, AlignSize>;
|
||||
};
|
||||
|
||||
} // namespace policy
|
||||
} // namespace ipc
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "def.h"
|
||||
#include "prod_cons.h"
|
||||
|
||||
#include "circ/elem_array.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace policy {
|
||||
|
||||
template <template <typename, std::size_t...> class Elems, typename Flag>
|
||||
struct choose;
|
||||
|
||||
template <typename Flag>
|
||||
struct choose<circ::elem_array, Flag> {
|
||||
template <std::size_t DataSize, std::size_t AlignSize>
|
||||
using elems_t = circ::elem_array<ipc::prod_cons_impl<Flag>, DataSize, AlignSize>;
|
||||
};
|
||||
|
||||
} // namespace policy
|
||||
} // namespace ipc
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
#include "pool_alloc.h"
|
||||
|
||||
#include "memory/resource.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
void pool_alloc::clear() {
|
||||
async_pool_alloc::clear();
|
||||
}
|
||||
|
||||
void* pool_alloc::alloc(std::size_t size) {
|
||||
return async_pool_alloc::alloc(size);
|
||||
}
|
||||
|
||||
void pool_alloc::free(void* p, std::size_t size) {
|
||||
async_pool_alloc::free(p, size);
|
||||
}
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
#include "pool_alloc.h"
|
||||
|
||||
#include "memory/resource.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
void pool_alloc::clear() {
|
||||
async_pool_alloc::clear();
|
||||
}
|
||||
|
||||
void* pool_alloc::alloc(std::size_t size) {
|
||||
return async_pool_alloc::alloc(size);
|
||||
}
|
||||
|
||||
void pool_alloc::free(void* p, std::size_t size) {
|
||||
async_pool_alloc::free(p, size);
|
||||
}
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
|
||||
758
src/prod_cons.h
758
src/prod_cons.h
@ -1,379 +1,379 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <utility>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
||||
#include "def.h"
|
||||
#include "platform/detail.h"
|
||||
#include "circ/elem_def.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// producer-consumer implementation
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename Flag>
|
||||
struct prod_cons_impl;
|
||||
|
||||
template <>
|
||||
struct prod_cons_impl<wr<relat::single, relat::single, trans::unicast>> {
|
||||
|
||||
template <std::size_t DataSize, std::size_t AlignSize>
|
||||
struct elem_t {
|
||||
std::aligned_storage_t<DataSize, AlignSize> data_ {};
|
||||
};
|
||||
|
||||
alignas(detail::cache_line_size) std::atomic<circ::u2_t> rd_; // read index
|
||||
alignas(detail::cache_line_size) std::atomic<circ::u2_t> wt_; // write index
|
||||
|
||||
constexpr circ::u2_t cursor() const noexcept {
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool push(W* /*wrapper*/, F&& f, E* elems) {
|
||||
auto cur_wt = circ::index_of(wt_.load(std::memory_order_relaxed));
|
||||
if (cur_wt == circ::index_of(rd_.load(std::memory_order_acquire) - 1)) {
|
||||
return false; // full
|
||||
}
|
||||
std::forward<F>(f)(&(elems[cur_wt].data_));
|
||||
wt_.fetch_add(1, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool force_push(W* wrapper, F&& f, E* elems) {
|
||||
return push(wrapper, std::forward<F>(f), elems);
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool pop(W* /*wrapper*/, circ::u2_t& /*cur*/, F&& f, E* elems) {
|
||||
auto cur_rd = circ::index_of(rd_.load(std::memory_order_relaxed));
|
||||
if (cur_rd == circ::index_of(wt_.load(std::memory_order_acquire))) {
|
||||
return false; // empty
|
||||
}
|
||||
std::forward<F>(f)(&(elems[cur_rd].data_));
|
||||
rd_.fetch_add(1, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct prod_cons_impl<wr<relat::single, relat::multi , trans::unicast>>
|
||||
: prod_cons_impl<wr<relat::single, relat::single, trans::unicast>> {
|
||||
|
||||
template <typename W, typename F, template <std::size_t, std::size_t> class E, std::size_t DS, std::size_t AS>
|
||||
bool pop(W* /*wrapper*/, circ::u2_t& /*cur*/, F&& f, E<DS, AS>* elems) {
|
||||
byte_t buff[DS];
|
||||
for (unsigned k = 0;;) {
|
||||
auto cur_rd = rd_.load(std::memory_order_relaxed);
|
||||
if (circ::index_of(cur_rd) ==
|
||||
circ::index_of(wt_.load(std::memory_order_acquire))) {
|
||||
return false; // empty
|
||||
}
|
||||
std::memcpy(buff, &(elems[circ::index_of(cur_rd)].data_), sizeof(buff));
|
||||
if (rd_.compare_exchange_weak(cur_rd, cur_rd + 1, std::memory_order_release)) {
|
||||
std::forward<F>(f)(buff);
|
||||
return true;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct prod_cons_impl<wr<relat::multi , relat::multi, trans::unicast>>
|
||||
: prod_cons_impl<wr<relat::single, relat::multi, trans::unicast>> {
|
||||
|
||||
using flag_t = std::uint64_t;
|
||||
|
||||
template <std::size_t DataSize, std::size_t AlignSize>
|
||||
struct elem_t {
|
||||
std::aligned_storage_t<DataSize, AlignSize> data_ {};
|
||||
std::atomic<flag_t> f_ct_ { 0 }; // commit flag
|
||||
};
|
||||
|
||||
alignas(detail::cache_line_size) std::atomic<circ::u2_t> ct_; // commit index
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool push(W* /*wrapper*/, F&& f, E* elems) {
|
||||
circ::u2_t cur_ct, nxt_ct;
|
||||
for (unsigned k = 0;;) {
|
||||
cur_ct = ct_.load(std::memory_order_relaxed);
|
||||
if (circ::index_of(nxt_ct = cur_ct + 1) ==
|
||||
circ::index_of(rd_.load(std::memory_order_acquire))) {
|
||||
return false; // full
|
||||
}
|
||||
if (ct_.compare_exchange_weak(cur_ct, nxt_ct, std::memory_order_release)) {
|
||||
break;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
auto* el = elems + circ::index_of(cur_ct);
|
||||
std::forward<F>(f)(&(el->data_));
|
||||
// set flag & try update wt
|
||||
el->f_ct_.store(~static_cast<flag_t>(cur_ct), std::memory_order_release);
|
||||
while (1) {
|
||||
auto cac_ct = el->f_ct_.load(std::memory_order_acquire);
|
||||
if (cur_ct != wt_.load(std::memory_order_acquire)) {
|
||||
return true;
|
||||
}
|
||||
if ((~cac_ct) != cur_ct) {
|
||||
return true;
|
||||
}
|
||||
if (!el->f_ct_.compare_exchange_strong(cac_ct, 0, std::memory_order_relaxed)) {
|
||||
return true;
|
||||
}
|
||||
wt_.store(nxt_ct, std::memory_order_release);
|
||||
cur_ct = nxt_ct;
|
||||
nxt_ct = cur_ct + 1;
|
||||
el = elems + circ::index_of(cur_ct);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool force_push(W* wrapper, F&& f, E* elems) {
|
||||
return push(wrapper, std::forward<F>(f), elems); /* TBD */
|
||||
}
|
||||
|
||||
template <typename W, typename F, template <std::size_t, std::size_t> class E, std::size_t DS, std::size_t AS>
|
||||
bool pop(W* /*wrapper*/, circ::u2_t& /*cur*/, F&& f, E<DS, AS>* elems) {
|
||||
byte_t buff[DS];
|
||||
for (unsigned k = 0;;) {
|
||||
auto cur_rd = rd_.load(std::memory_order_relaxed);
|
||||
auto cur_wt = wt_.load(std::memory_order_acquire);
|
||||
auto id_rd = circ::index_of(cur_rd);
|
||||
auto id_wt = circ::index_of(cur_wt);
|
||||
if (id_rd == id_wt) {
|
||||
auto* el = elems + id_wt;
|
||||
auto cac_ct = el->f_ct_.load(std::memory_order_acquire);
|
||||
if ((~cac_ct) != cur_wt) {
|
||||
return false; // empty
|
||||
}
|
||||
if (el->f_ct_.compare_exchange_weak(cac_ct, 0, std::memory_order_relaxed)) {
|
||||
wt_.store(cur_wt + 1, std::memory_order_release);
|
||||
}
|
||||
k = 0;
|
||||
}
|
||||
else {
|
||||
std::memcpy(buff, &(elems[circ::index_of(cur_rd)].data_), sizeof(buff));
|
||||
if (rd_.compare_exchange_weak(cur_rd, cur_rd + 1, std::memory_order_release)) {
|
||||
std::forward<F>(f)(buff);
|
||||
return true;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct prod_cons_impl<wr<relat::single, relat::multi, trans::broadcast>> {
|
||||
|
||||
using rc_t = std::size_t;
|
||||
|
||||
template <std::size_t DataSize, std::size_t AlignSize>
|
||||
struct elem_t {
|
||||
std::aligned_storage_t<DataSize, AlignSize> data_ {};
|
||||
std::atomic<rc_t> rc_ { 0 }; // read-counter
|
||||
};
|
||||
|
||||
alignas(detail::cache_line_size) std::atomic<circ::u2_t> wt_; // write index
|
||||
|
||||
circ::u2_t cursor() const noexcept {
|
||||
return wt_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool push(W* wrapper, F&& f, E* elems) {
|
||||
E* el;
|
||||
for (unsigned k = 0;;) {
|
||||
auto cc = wrapper->conn_count(std::memory_order_relaxed);
|
||||
if (cc == 0) return false; // no reader
|
||||
el = elems + circ::index_of(wt_.load(std::memory_order_acquire));
|
||||
// check all consumers have finished reading this element
|
||||
auto cur_rc = el->rc_.load(std::memory_order_acquire);
|
||||
if (cur_rc) {
|
||||
return false; // full
|
||||
}
|
||||
// cur_rc should be 0 here
|
||||
if (el->rc_.compare_exchange_weak(
|
||||
cur_rc, static_cast<rc_t>(cc), std::memory_order_release)) {
|
||||
wrapper->clear_dis_flag(std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
std::forward<F>(f)(&(el->data_));
|
||||
wt_.fetch_add(1, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool force_push(W* wrapper, F&& f, E* elems) {
|
||||
E* el;
|
||||
for (unsigned k = 0;;) {
|
||||
auto cc = wrapper->conn_count(std::memory_order_relaxed);
|
||||
if (cc == 0) return false; // no reader
|
||||
el = elems + circ::index_of(wt_.load(std::memory_order_acquire));
|
||||
// check all consumers have finished reading this element
|
||||
auto cur_rc = el->rc_.load(std::memory_order_acquire);
|
||||
if (cur_rc) {
|
||||
wrapper->try_disconnect(); // try disconnect a reader
|
||||
cc = wrapper->conn_count(std::memory_order_relaxed);
|
||||
if (cc == 0) return false; // no reader
|
||||
}
|
||||
// just compare & exchange
|
||||
if (el->rc_.compare_exchange_weak(
|
||||
cur_rc, static_cast<rc_t>(cc), std::memory_order_release)) {
|
||||
break;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
std::forward<F>(f)(&(el->data_));
|
||||
wt_.fetch_add(1, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool pop(W* /*wrapper*/, circ::u2_t& cur, F&& f, E* elems) {
|
||||
if (cur == cursor()) return false; // acquire
|
||||
auto* el = elems + circ::index_of(cur++);
|
||||
std::forward<F>(f)(&(el->data_));
|
||||
for (unsigned k = 0;;) {
|
||||
rc_t cur_rc = el->rc_.load(std::memory_order_acquire);
|
||||
if (cur_rc == 0) {
|
||||
return true;
|
||||
}
|
||||
if (el->rc_.compare_exchange_weak(
|
||||
cur_rc, cur_rc - 1, std::memory_order_release)) {
|
||||
return true;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct prod_cons_impl<wr<relat::multi , relat::multi, trans::broadcast>> {
|
||||
|
||||
using rc_t = std::uint64_t;
|
||||
using flag_t = std::uint64_t;
|
||||
|
||||
enum : rc_t {
|
||||
rc_mask = 0x00000000ffffffffull,
|
||||
rc_incr = 0x0000000100000000ull
|
||||
};
|
||||
|
||||
template <std::size_t DataSize, std::size_t AlignSize>
|
||||
struct elem_t {
|
||||
std::aligned_storage_t<DataSize, AlignSize> data_ {};
|
||||
std::atomic<rc_t > rc_ { 0 }; // read-counter
|
||||
std::atomic<flag_t> f_ct_ { 0 }; // commit flag
|
||||
};
|
||||
|
||||
alignas(detail::cache_line_size) std::atomic<circ::u2_t> ct_; // commit index
|
||||
|
||||
circ::u2_t cursor() const noexcept {
|
||||
return ct_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool push(W* wrapper, F&& f, E* elems) {
|
||||
E* el;
|
||||
circ::u2_t cur_ct;
|
||||
for (unsigned k = 0;;) {
|
||||
auto cc = wrapper->conn_count(std::memory_order_relaxed);
|
||||
if (cc == 0) return false; // no reader
|
||||
el = elems + circ::index_of(cur_ct = ct_.load(std::memory_order_relaxed));
|
||||
// check all consumers have finished reading this element
|
||||
auto cur_rc = el->rc_.load(std::memory_order_acquire);
|
||||
if (cur_rc & rc_mask) {
|
||||
return false; // full
|
||||
}
|
||||
auto cur_fl = el->f_ct_.load(std::memory_order_acquire);
|
||||
if ((cur_fl != cur_ct) && cur_fl) {
|
||||
return false; // full
|
||||
}
|
||||
// (cur_rc & rc_mask) should be 0 here
|
||||
if (el->rc_.compare_exchange_weak(
|
||||
cur_rc, static_cast<rc_t>(cc) | ((cur_rc & ~rc_mask) + rc_incr), std::memory_order_release)) {
|
||||
wrapper->clear_dis_flag(std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
// only one thread/process would touch here at one time
|
||||
ct_.store(cur_ct + 1, std::memory_order_release);
|
||||
std::forward<F>(f)(&(el->data_));
|
||||
// set flag & try update wt
|
||||
el->f_ct_.store(~static_cast<flag_t>(cur_ct), std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool force_push(W* wrapper, F&& f, E* elems) {
|
||||
E* el;
|
||||
circ::u2_t cur_ct;
|
||||
for (unsigned k = 0;;) {
|
||||
auto cc = wrapper->conn_count(std::memory_order_relaxed);
|
||||
if (cc == 0) return false; // no reader
|
||||
el = elems + circ::index_of(cur_ct = ct_.load(std::memory_order_relaxed));
|
||||
// check all consumers have finished reading this element
|
||||
auto cur_rc = el->rc_.load(std::memory_order_acquire);
|
||||
ipc::log("force_push: k = %d, cc = %zd, rc = %zd\n", k, cc, (cur_rc & rc_mask));
|
||||
if (cur_rc & rc_mask) {
|
||||
wrapper->try_disconnect(); // try disconnect a reader
|
||||
cc = wrapper->conn_count(std::memory_order_relaxed);
|
||||
if (cc == 0) return false; // no reader
|
||||
}
|
||||
// just compare & exchange
|
||||
if (el->rc_.compare_exchange_weak(
|
||||
cur_rc, static_cast<rc_t>(cc) | ((cur_rc & ~rc_mask) + rc_incr), std::memory_order_release)) {
|
||||
break;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
// only one thread/process would touch here at one time
|
||||
ct_.store(cur_ct + 1, std::memory_order_release);
|
||||
std::forward<F>(f)(&(el->data_));
|
||||
// set flag & try update wt
|
||||
el->f_ct_.store(~static_cast<flag_t>(cur_ct), std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E, std::size_t N>
|
||||
bool pop(W* /*wrapper*/, circ::u2_t& cur, F&& f, E(& elems)[N]) {
|
||||
auto* el = elems + circ::index_of(cur);
|
||||
auto cur_fl = el->f_ct_.load(std::memory_order_acquire);
|
||||
if (cur_fl != ~static_cast<flag_t>(cur)) {
|
||||
return false; // empty
|
||||
}
|
||||
++cur;
|
||||
std::forward<F>(f)(&(el->data_));
|
||||
for (unsigned k = 0;;) {
|
||||
auto cur_rc = el->rc_.load(std::memory_order_acquire);
|
||||
switch (cur_rc & rc_mask) {
|
||||
case 0:
|
||||
el->f_ct_.store(cur + N - 1, std::memory_order_release);
|
||||
return true;
|
||||
case 1:
|
||||
el->f_ct_.store(cur + N - 1, std::memory_order_release);
|
||||
IPC_FALLTHROUGH_;
|
||||
default:
|
||||
if (el->rc_.compare_exchange_weak(
|
||||
cur_rc, cur_rc + rc_incr - 1, std::memory_order_release)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <utility>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
||||
#include "def.h"
|
||||
#include "platform/detail.h"
|
||||
#include "circ/elem_def.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// producer-consumer implementation
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename Flag>
|
||||
struct prod_cons_impl;
|
||||
|
||||
template <>
|
||||
struct prod_cons_impl<wr<relat::single, relat::single, trans::unicast>> {
|
||||
|
||||
template <std::size_t DataSize, std::size_t AlignSize>
|
||||
struct elem_t {
|
||||
std::aligned_storage_t<DataSize, AlignSize> data_ {};
|
||||
};
|
||||
|
||||
alignas(detail::cache_line_size) std::atomic<circ::u2_t> rd_; // read index
|
||||
alignas(detail::cache_line_size) std::atomic<circ::u2_t> wt_; // write index
|
||||
|
||||
constexpr circ::u2_t cursor() const noexcept {
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool push(W* /*wrapper*/, F&& f, E* elems) {
|
||||
auto cur_wt = circ::index_of(wt_.load(std::memory_order_relaxed));
|
||||
if (cur_wt == circ::index_of(rd_.load(std::memory_order_acquire) - 1)) {
|
||||
return false; // full
|
||||
}
|
||||
std::forward<F>(f)(&(elems[cur_wt].data_));
|
||||
wt_.fetch_add(1, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool force_push(W* wrapper, F&& f, E* elems) {
|
||||
return push(wrapper, std::forward<F>(f), elems);
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool pop(W* /*wrapper*/, circ::u2_t& /*cur*/, F&& f, E* elems) {
|
||||
auto cur_rd = circ::index_of(rd_.load(std::memory_order_relaxed));
|
||||
if (cur_rd == circ::index_of(wt_.load(std::memory_order_acquire))) {
|
||||
return false; // empty
|
||||
}
|
||||
std::forward<F>(f)(&(elems[cur_rd].data_));
|
||||
rd_.fetch_add(1, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct prod_cons_impl<wr<relat::single, relat::multi , trans::unicast>>
|
||||
: prod_cons_impl<wr<relat::single, relat::single, trans::unicast>> {
|
||||
|
||||
template <typename W, typename F, template <std::size_t, std::size_t> class E, std::size_t DS, std::size_t AS>
|
||||
bool pop(W* /*wrapper*/, circ::u2_t& /*cur*/, F&& f, E<DS, AS>* elems) {
|
||||
byte_t buff[DS];
|
||||
for (unsigned k = 0;;) {
|
||||
auto cur_rd = rd_.load(std::memory_order_relaxed);
|
||||
if (circ::index_of(cur_rd) ==
|
||||
circ::index_of(wt_.load(std::memory_order_acquire))) {
|
||||
return false; // empty
|
||||
}
|
||||
std::memcpy(buff, &(elems[circ::index_of(cur_rd)].data_), sizeof(buff));
|
||||
if (rd_.compare_exchange_weak(cur_rd, cur_rd + 1, std::memory_order_release)) {
|
||||
std::forward<F>(f)(buff);
|
||||
return true;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct prod_cons_impl<wr<relat::multi , relat::multi, trans::unicast>>
|
||||
: prod_cons_impl<wr<relat::single, relat::multi, trans::unicast>> {
|
||||
|
||||
using flag_t = std::uint64_t;
|
||||
|
||||
template <std::size_t DataSize, std::size_t AlignSize>
|
||||
struct elem_t {
|
||||
std::aligned_storage_t<DataSize, AlignSize> data_ {};
|
||||
std::atomic<flag_t> f_ct_ { 0 }; // commit flag
|
||||
};
|
||||
|
||||
alignas(detail::cache_line_size) std::atomic<circ::u2_t> ct_; // commit index
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool push(W* /*wrapper*/, F&& f, E* elems) {
|
||||
circ::u2_t cur_ct, nxt_ct;
|
||||
for (unsigned k = 0;;) {
|
||||
cur_ct = ct_.load(std::memory_order_relaxed);
|
||||
if (circ::index_of(nxt_ct = cur_ct + 1) ==
|
||||
circ::index_of(rd_.load(std::memory_order_acquire))) {
|
||||
return false; // full
|
||||
}
|
||||
if (ct_.compare_exchange_weak(cur_ct, nxt_ct, std::memory_order_release)) {
|
||||
break;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
auto* el = elems + circ::index_of(cur_ct);
|
||||
std::forward<F>(f)(&(el->data_));
|
||||
// set flag & try update wt
|
||||
el->f_ct_.store(~static_cast<flag_t>(cur_ct), std::memory_order_release);
|
||||
while (1) {
|
||||
auto cac_ct = el->f_ct_.load(std::memory_order_acquire);
|
||||
if (cur_ct != wt_.load(std::memory_order_acquire)) {
|
||||
return true;
|
||||
}
|
||||
if ((~cac_ct) != cur_ct) {
|
||||
return true;
|
||||
}
|
||||
if (!el->f_ct_.compare_exchange_strong(cac_ct, 0, std::memory_order_relaxed)) {
|
||||
return true;
|
||||
}
|
||||
wt_.store(nxt_ct, std::memory_order_release);
|
||||
cur_ct = nxt_ct;
|
||||
nxt_ct = cur_ct + 1;
|
||||
el = elems + circ::index_of(cur_ct);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool force_push(W* wrapper, F&& f, E* elems) {
|
||||
return push(wrapper, std::forward<F>(f), elems); /* TBD */
|
||||
}
|
||||
|
||||
template <typename W, typename F, template <std::size_t, std::size_t> class E, std::size_t DS, std::size_t AS>
|
||||
bool pop(W* /*wrapper*/, circ::u2_t& /*cur*/, F&& f, E<DS, AS>* elems) {
|
||||
byte_t buff[DS];
|
||||
for (unsigned k = 0;;) {
|
||||
auto cur_rd = rd_.load(std::memory_order_relaxed);
|
||||
auto cur_wt = wt_.load(std::memory_order_acquire);
|
||||
auto id_rd = circ::index_of(cur_rd);
|
||||
auto id_wt = circ::index_of(cur_wt);
|
||||
if (id_rd == id_wt) {
|
||||
auto* el = elems + id_wt;
|
||||
auto cac_ct = el->f_ct_.load(std::memory_order_acquire);
|
||||
if ((~cac_ct) != cur_wt) {
|
||||
return false; // empty
|
||||
}
|
||||
if (el->f_ct_.compare_exchange_weak(cac_ct, 0, std::memory_order_relaxed)) {
|
||||
wt_.store(cur_wt + 1, std::memory_order_release);
|
||||
}
|
||||
k = 0;
|
||||
}
|
||||
else {
|
||||
std::memcpy(buff, &(elems[circ::index_of(cur_rd)].data_), sizeof(buff));
|
||||
if (rd_.compare_exchange_weak(cur_rd, cur_rd + 1, std::memory_order_release)) {
|
||||
std::forward<F>(f)(buff);
|
||||
return true;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct prod_cons_impl<wr<relat::single, relat::multi, trans::broadcast>> {
|
||||
|
||||
using rc_t = std::uint32_t;
|
||||
|
||||
template <std::size_t DataSize, std::size_t AlignSize>
|
||||
struct elem_t {
|
||||
std::aligned_storage_t<DataSize, AlignSize> data_ {};
|
||||
std::atomic<rc_t> rc_ { 0 }; // read-counter
|
||||
};
|
||||
|
||||
alignas(detail::cache_line_size) std::atomic<circ::u2_t> wt_; // write index
|
||||
|
||||
circ::u2_t cursor() const noexcept {
|
||||
return wt_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool push(W* wrapper, F&& f, E* elems) {
|
||||
E* el;
|
||||
for (unsigned k = 0;;) {
|
||||
auto cc = wrapper->conn_count(std::memory_order_relaxed);
|
||||
if (cc == 0) return false; // no reader
|
||||
el = elems + circ::index_of(wt_.load(std::memory_order_acquire));
|
||||
// check all consumers have finished reading this element
|
||||
auto cur_rc = el->rc_.load(std::memory_order_acquire);
|
||||
if (cur_rc) {
|
||||
return false; // full
|
||||
}
|
||||
// cur_rc should be 0 here
|
||||
if (el->rc_.compare_exchange_weak(
|
||||
cur_rc, static_cast<rc_t>(cc), std::memory_order_release)) {
|
||||
wrapper->clear_dis_flag(std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
std::forward<F>(f)(&(el->data_));
|
||||
wt_.fetch_add(1, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool force_push(W* wrapper, F&& f, E* elems) {
|
||||
E* el;
|
||||
for (unsigned k = 0;;) {
|
||||
auto cc = wrapper->conn_count(std::memory_order_relaxed);
|
||||
if (cc == 0) return false; // no reader
|
||||
el = elems + circ::index_of(wt_.load(std::memory_order_acquire));
|
||||
// check all consumers have finished reading this element
|
||||
auto cur_rc = el->rc_.load(std::memory_order_acquire);
|
||||
if (cur_rc) {
|
||||
wrapper->try_disconnect(); // try disconnect a reader
|
||||
cc = wrapper->conn_count(std::memory_order_relaxed);
|
||||
if (cc == 0) return false; // no reader
|
||||
}
|
||||
// just compare & exchange
|
||||
if (el->rc_.compare_exchange_weak(
|
||||
cur_rc, static_cast<rc_t>(cc), std::memory_order_release)) {
|
||||
break;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
std::forward<F>(f)(&(el->data_));
|
||||
wt_.fetch_add(1, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool pop(W* /*wrapper*/, circ::u2_t& cur, F&& f, E* elems) {
|
||||
if (cur == cursor()) return false; // acquire
|
||||
auto* el = elems + circ::index_of(cur++);
|
||||
std::forward<F>(f)(&(el->data_));
|
||||
for (unsigned k = 0;;) {
|
||||
rc_t cur_rc = el->rc_.load(std::memory_order_acquire);
|
||||
if (cur_rc == 0) {
|
||||
return true;
|
||||
}
|
||||
if (el->rc_.compare_exchange_weak(
|
||||
cur_rc, cur_rc - 1, std::memory_order_release)) {
|
||||
return true;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct prod_cons_impl<wr<relat::multi , relat::multi, trans::broadcast>> {
|
||||
|
||||
using rc_t = std::uint64_t;
|
||||
using flag_t = std::uint64_t;
|
||||
|
||||
enum : rc_t {
|
||||
rc_mask = 0x00000000ffffffffull,
|
||||
rc_incr = 0x0000000100000000ull
|
||||
};
|
||||
|
||||
template <std::size_t DataSize, std::size_t AlignSize>
|
||||
struct elem_t {
|
||||
std::aligned_storage_t<DataSize, AlignSize> data_ {};
|
||||
std::atomic<rc_t > rc_ { 0 }; // read-counter
|
||||
std::atomic<flag_t> f_ct_ { 0 }; // commit flag
|
||||
};
|
||||
|
||||
alignas(detail::cache_line_size) std::atomic<circ::u2_t> ct_; // commit index
|
||||
|
||||
circ::u2_t cursor() const noexcept {
|
||||
return ct_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool push(W* wrapper, F&& f, E* elems) {
|
||||
E* el;
|
||||
circ::u2_t cur_ct;
|
||||
for (unsigned k = 0;;) {
|
||||
auto cc = wrapper->conn_count(std::memory_order_relaxed);
|
||||
if (cc == 0) return false; // no reader
|
||||
el = elems + circ::index_of(cur_ct = ct_.load(std::memory_order_relaxed));
|
||||
// check all consumers have finished reading this element
|
||||
auto cur_rc = el->rc_.load(std::memory_order_acquire);
|
||||
if (cur_rc & rc_mask) {
|
||||
return false; // full
|
||||
}
|
||||
auto cur_fl = el->f_ct_.load(std::memory_order_acquire);
|
||||
if ((cur_fl != cur_ct) && cur_fl) {
|
||||
return false; // full
|
||||
}
|
||||
// (cur_rc & rc_mask) should be 0 here
|
||||
if (el->rc_.compare_exchange_weak(
|
||||
cur_rc, static_cast<rc_t>(cc) | ((cur_rc & ~rc_mask) + rc_incr), std::memory_order_release)) {
|
||||
wrapper->clear_dis_flag(std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
// only one thread/process would touch here at one time
|
||||
ct_.store(cur_ct + 1, std::memory_order_release);
|
||||
std::forward<F>(f)(&(el->data_));
|
||||
// set flag & try update wt
|
||||
el->f_ct_.store(~static_cast<flag_t>(cur_ct), std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool force_push(W* wrapper, F&& f, E* elems) {
|
||||
E* el;
|
||||
circ::u2_t cur_ct;
|
||||
for (unsigned k = 0;;) {
|
||||
auto cc = wrapper->conn_count(std::memory_order_relaxed);
|
||||
if (cc == 0) return false; // no reader
|
||||
el = elems + circ::index_of(cur_ct = ct_.load(std::memory_order_relaxed));
|
||||
// check all consumers have finished reading this element
|
||||
auto cur_rc = el->rc_.load(std::memory_order_acquire);
|
||||
ipc::log("force_push: k = %d, cc = %zd, rc = %zd\n", k, cc, (cur_rc & rc_mask));
|
||||
if (cur_rc & rc_mask) {
|
||||
wrapper->try_disconnect(); // try disconnect a reader
|
||||
cc = wrapper->conn_count(std::memory_order_relaxed);
|
||||
if (cc == 0) return false; // no reader
|
||||
}
|
||||
// just compare & exchange
|
||||
if (el->rc_.compare_exchange_weak(
|
||||
cur_rc, static_cast<rc_t>(cc) | ((cur_rc & ~rc_mask) + rc_incr), std::memory_order_release)) {
|
||||
break;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
// only one thread/process would touch here at one time
|
||||
ct_.store(cur_ct + 1, std::memory_order_release);
|
||||
std::forward<F>(f)(&(el->data_));
|
||||
// set flag & try update wt
|
||||
el->f_ct_.store(~static_cast<flag_t>(cur_ct), std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename W, typename F, typename E, std::size_t N>
|
||||
bool pop(W* /*wrapper*/, circ::u2_t& cur, F&& f, E(& elems)[N]) {
|
||||
auto* el = elems + circ::index_of(cur);
|
||||
auto cur_fl = el->f_ct_.load(std::memory_order_acquire);
|
||||
if (cur_fl != ~static_cast<flag_t>(cur)) {
|
||||
return false; // empty
|
||||
}
|
||||
++cur;
|
||||
std::forward<F>(f)(&(el->data_));
|
||||
for (unsigned k = 0;;) {
|
||||
auto cur_rc = el->rc_.load(std::memory_order_acquire);
|
||||
switch (cur_rc & rc_mask) {
|
||||
case 0:
|
||||
el->f_ct_.store(cur + N - 1, std::memory_order_release);
|
||||
return true;
|
||||
case 1:
|
||||
el->f_ct_.store(cur + N - 1, std::memory_order_release);
|
||||
IPC_FALLTHROUGH_;
|
||||
default:
|
||||
if (el->rc_.compare_exchange_weak(
|
||||
cur_rc, cur_rc + rc_incr - 1, std::memory_order_release)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
394
src/queue.h
394
src/queue.h
@ -1,197 +1,197 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <new>
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <tuple>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
#include "def.h"
|
||||
#include "shm.h"
|
||||
#include "log.h"
|
||||
#include "rw_lock.h"
|
||||
|
||||
#include "platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
class queue_conn {
|
||||
protected:
|
||||
bool connected_ = false;
|
||||
shm::handle elems_h_;
|
||||
|
||||
template <typename Elems>
|
||||
Elems* open(char const * name) {
|
||||
if (name == nullptr || name[0] == '\0') {
|
||||
ipc::error("fail open waiter: name is empty!\n");
|
||||
return nullptr;
|
||||
}
|
||||
if (!elems_h_.acquire(name, sizeof(Elems))) {
|
||||
return nullptr;
|
||||
}
|
||||
auto elems = static_cast<Elems*>(elems_h_.get());
|
||||
if (elems == nullptr) {
|
||||
ipc::error("fail acquire elems: %s\n", name);
|
||||
return nullptr;
|
||||
}
|
||||
elems->init();
|
||||
return elems;
|
||||
}
|
||||
|
||||
void close() {
|
||||
elems_h_.release();
|
||||
}
|
||||
|
||||
public:
|
||||
queue_conn() = default;
|
||||
queue_conn(const queue_conn&) = delete;
|
||||
queue_conn& operator=(const queue_conn&) = delete;
|
||||
|
||||
bool connected() const noexcept {
|
||||
return connected_;
|
||||
}
|
||||
|
||||
template <typename Elems>
|
||||
auto connect(Elems* elems)
|
||||
-> std::tuple<bool, decltype(std::declval<Elems>().cursor())> {
|
||||
if (elems == nullptr) return {};
|
||||
if (connected_) {
|
||||
// if it's already connected, just return false
|
||||
return {};
|
||||
}
|
||||
connected_ = true;
|
||||
elems->connect();
|
||||
return std::make_tuple(true, elems->cursor());
|
||||
}
|
||||
|
||||
template <typename Elems>
|
||||
bool disconnect(Elems* elems) {
|
||||
if (elems == nullptr) return false;
|
||||
if (!connected_) {
|
||||
// if it's already disconnected, just return false
|
||||
return false;
|
||||
}
|
||||
connected_ = false;
|
||||
elems->disconnect();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Elems>
|
||||
class queue_base : public queue_conn {
|
||||
using base_t = queue_conn;
|
||||
|
||||
public:
|
||||
using elems_t = Elems;
|
||||
using policy_t = typename elems_t::policy_t;
|
||||
|
||||
protected:
|
||||
elems_t * elems_ = nullptr;
|
||||
decltype(std::declval<elems_t>().cursor()) cursor_ = 0;
|
||||
|
||||
public:
|
||||
using base_t::base_t;
|
||||
|
||||
queue_base() = default;
|
||||
|
||||
explicit queue_base(char const * name)
|
||||
: queue_base() {
|
||||
elems_ = open<elems_t>(name);
|
||||
}
|
||||
|
||||
/* not virtual */ ~queue_base() {
|
||||
base_t::close();
|
||||
}
|
||||
|
||||
constexpr elems_t * elems() const noexcept {
|
||||
return elems_;
|
||||
}
|
||||
|
||||
bool connect() {
|
||||
auto tp = base_t::connect(elems_);
|
||||
if (std::get<0>(tp)) {
|
||||
cursor_ = std::get<1>(tp);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool disconnect() {
|
||||
return base_t::disconnect(elems_);
|
||||
}
|
||||
|
||||
bool dis_flag() {
|
||||
return elems_->dis_flag();
|
||||
}
|
||||
|
||||
std::size_t conn_count() const noexcept {
|
||||
return (elems_ == nullptr) ? invalid_value : elems_->conn_count();
|
||||
}
|
||||
|
||||
bool valid() const noexcept {
|
||||
return elems_ != nullptr;
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return (elems_ == nullptr) ? true : (cursor_ == elems_->cursor());
|
||||
}
|
||||
|
||||
template <typename T, typename... P>
|
||||
auto push(P&&... params) {
|
||||
if (elems_ == nullptr) return false;
|
||||
return elems_->push([&](void* p) {
|
||||
::new (p) T(std::forward<P>(params)...);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T, typename... P>
|
||||
auto force_push(P&&... params) {
|
||||
if (elems_ == nullptr) return false;
|
||||
return elems_->force_push([&](void* p) {
|
||||
::new (p) T(std::forward<P>(params)...);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool pop(T& item) {
|
||||
if (elems_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return elems_->pop(&(this->cursor_), [&item](void* p) {
|
||||
::new (&item) T(std::move(*static_cast<T*>(p)));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T, typename Policy>
|
||||
class queue : public detail::queue_base<typename Policy::template elems_t<sizeof(T), alignof(T)>> {
|
||||
using base_t = detail::queue_base<typename Policy::template elems_t<sizeof(T), alignof(T)>>;
|
||||
|
||||
public:
|
||||
using value_t = T;
|
||||
|
||||
using base_t::base_t;
|
||||
|
||||
template <typename... P>
|
||||
auto push(P&&... params) {
|
||||
return base_t::template push<T>(std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
template <typename... P>
|
||||
auto force_push(P&&... params) {
|
||||
return base_t::template force_push<T>(std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
bool pop(T& item) {
|
||||
return base_t::pop(item);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <new>
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <tuple>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
#include "def.h"
|
||||
#include "shm.h"
|
||||
#include "log.h"
|
||||
#include "rw_lock.h"
|
||||
|
||||
#include "platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
class queue_conn {
|
||||
protected:
|
||||
bool connected_ = false;
|
||||
shm::handle elems_h_;
|
||||
|
||||
template <typename Elems>
|
||||
Elems* open(char const * name) {
|
||||
if (name == nullptr || name[0] == '\0') {
|
||||
ipc::error("fail open waiter: name is empty!\n");
|
||||
return nullptr;
|
||||
}
|
||||
if (!elems_h_.acquire(name, sizeof(Elems))) {
|
||||
return nullptr;
|
||||
}
|
||||
auto elems = static_cast<Elems*>(elems_h_.get());
|
||||
if (elems == nullptr) {
|
||||
ipc::error("fail acquire elems: %s\n", name);
|
||||
return nullptr;
|
||||
}
|
||||
elems->init();
|
||||
return elems;
|
||||
}
|
||||
|
||||
void close() {
|
||||
elems_h_.release();
|
||||
}
|
||||
|
||||
public:
|
||||
queue_conn() = default;
|
||||
queue_conn(const queue_conn&) = delete;
|
||||
queue_conn& operator=(const queue_conn&) = delete;
|
||||
|
||||
bool connected() const noexcept {
|
||||
return connected_;
|
||||
}
|
||||
|
||||
template <typename Elems>
|
||||
auto connect(Elems* elems)
|
||||
-> std::tuple<bool, decltype(std::declval<Elems>().cursor())> {
|
||||
if (elems == nullptr) return {};
|
||||
if (connected_) {
|
||||
// if it's already connected, just return false
|
||||
return {};
|
||||
}
|
||||
connected_ = true;
|
||||
elems->connect();
|
||||
return std::make_tuple(true, elems->cursor());
|
||||
}
|
||||
|
||||
template <typename Elems>
|
||||
bool disconnect(Elems* elems) {
|
||||
if (elems == nullptr) return false;
|
||||
if (!connected_) {
|
||||
// if it's already disconnected, just return false
|
||||
return false;
|
||||
}
|
||||
connected_ = false;
|
||||
elems->disconnect();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Elems>
|
||||
class queue_base : public queue_conn {
|
||||
using base_t = queue_conn;
|
||||
|
||||
public:
|
||||
using elems_t = Elems;
|
||||
using policy_t = typename elems_t::policy_t;
|
||||
|
||||
protected:
|
||||
elems_t * elems_ = nullptr;
|
||||
decltype(std::declval<elems_t>().cursor()) cursor_ = 0;
|
||||
|
||||
public:
|
||||
using base_t::base_t;
|
||||
|
||||
queue_base() = default;
|
||||
|
||||
explicit queue_base(char const * name)
|
||||
: queue_base() {
|
||||
elems_ = open<elems_t>(name);
|
||||
}
|
||||
|
||||
/* not virtual */ ~queue_base() {
|
||||
base_t::close();
|
||||
}
|
||||
|
||||
constexpr elems_t * elems() const noexcept {
|
||||
return elems_;
|
||||
}
|
||||
|
||||
bool connect() {
|
||||
auto tp = base_t::connect(elems_);
|
||||
if (std::get<0>(tp)) {
|
||||
cursor_ = std::get<1>(tp);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool disconnect() {
|
||||
return base_t::disconnect(elems_);
|
||||
}
|
||||
|
||||
bool dis_flag() {
|
||||
return elems_->dis_flag();
|
||||
}
|
||||
|
||||
std::size_t conn_count() const noexcept {
|
||||
return (elems_ == nullptr) ? invalid_value : elems_->conn_count();
|
||||
}
|
||||
|
||||
bool valid() const noexcept {
|
||||
return elems_ != nullptr;
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return (elems_ == nullptr) ? true : (cursor_ == elems_->cursor());
|
||||
}
|
||||
|
||||
template <typename T, typename... P>
|
||||
auto push(P&&... params) {
|
||||
if (elems_ == nullptr) return false;
|
||||
return elems_->push([&](void* p) {
|
||||
::new (p) T(std::forward<P>(params)...);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T, typename... P>
|
||||
auto force_push(P&&... params) {
|
||||
if (elems_ == nullptr) return false;
|
||||
return elems_->force_push([&](void* p) {
|
||||
::new (p) T(std::forward<P>(params)...);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool pop(T& item) {
|
||||
if (elems_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return elems_->pop(&(this->cursor_), [&item](void* p) {
|
||||
::new (&item) T(std::move(*static_cast<T*>(p)));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T, typename Policy>
|
||||
class queue : public detail::queue_base<typename Policy::template elems_t<sizeof(T), alignof(T)>> {
|
||||
using base_t = detail::queue_base<typename Policy::template elems_t<sizeof(T), alignof(T)>>;
|
||||
|
||||
public:
|
||||
using value_t = T;
|
||||
|
||||
using base_t::base_t;
|
||||
|
||||
template <typename... P>
|
||||
auto push(P&&... params) {
|
||||
return base_t::template push<T>(std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
template <typename... P>
|
||||
auto force_push(P&&... params) {
|
||||
return base_t::template force_push<T>(std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
bool pop(T& item) {
|
||||
return base_t::pop(item);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
188
src/shm.cpp
188
src/shm.cpp
@ -1,94 +1,94 @@
|
||||
#include "shm.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "pimpl.h"
|
||||
#include "memory/resource.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace shm {
|
||||
|
||||
class handle::handle_ : public pimpl<handle_> {
|
||||
public:
|
||||
shm::id_t id_ = nullptr;
|
||||
void* m_ = nullptr;
|
||||
|
||||
ipc::string n_;
|
||||
std::size_t s_ = 0;
|
||||
};
|
||||
|
||||
handle::handle()
|
||||
: p_(p_->make()) {
|
||||
}
|
||||
|
||||
handle::handle(char const * name, std::size_t size, unsigned mode)
|
||||
: handle() {
|
||||
acquire(name, size, mode);
|
||||
}
|
||||
|
||||
handle::handle(handle&& rhs)
|
||||
: handle() {
|
||||
swap(rhs);
|
||||
}
|
||||
|
||||
handle::~handle() {
|
||||
release();
|
||||
p_->clear();
|
||||
}
|
||||
|
||||
void handle::swap(handle& rhs) {
|
||||
std::swap(p_, rhs.p_);
|
||||
}
|
||||
|
||||
handle& handle::operator=(handle rhs) {
|
||||
swap(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool handle::valid() const {
|
||||
return impl(p_)->m_ != nullptr;
|
||||
}
|
||||
|
||||
std::size_t handle::size() const {
|
||||
return impl(p_)->s_;
|
||||
}
|
||||
|
||||
char const * handle::name() const {
|
||||
return impl(p_)->n_.c_str();
|
||||
}
|
||||
|
||||
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);
|
||||
impl(p_)->m_ = shm::get_mem(impl(p_)->id_, &(impl(p_)->s_));
|
||||
return valid();
|
||||
}
|
||||
|
||||
void handle::release() {
|
||||
if (impl(p_)->id_ == nullptr) return;
|
||||
shm::release(detach());
|
||||
}
|
||||
|
||||
void* handle::get() const {
|
||||
return impl(p_)->m_;
|
||||
}
|
||||
|
||||
void handle::attach(id_t id) {
|
||||
if (id == nullptr) return;
|
||||
release();
|
||||
impl(p_)->id_ = id;
|
||||
impl(p_)->m_ = shm::get_mem(impl(p_)->id_, &(impl(p_)->s_));
|
||||
}
|
||||
|
||||
id_t handle::detach() {
|
||||
auto old = impl(p_)->id_;
|
||||
impl(p_)->id_ = nullptr;
|
||||
impl(p_)->m_ = nullptr;
|
||||
impl(p_)->s_ = 0;
|
||||
impl(p_)->n_.clear();
|
||||
return old;
|
||||
}
|
||||
|
||||
} // namespace shm
|
||||
} // namespace ipc
|
||||
#include "shm.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "pimpl.h"
|
||||
#include "memory/resource.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace shm {
|
||||
|
||||
class handle::handle_ : public pimpl<handle_> {
|
||||
public:
|
||||
shm::id_t id_ = nullptr;
|
||||
void* m_ = nullptr;
|
||||
|
||||
ipc::string n_;
|
||||
std::size_t s_ = 0;
|
||||
};
|
||||
|
||||
handle::handle()
|
||||
: p_(p_->make()) {
|
||||
}
|
||||
|
||||
handle::handle(char const * name, std::size_t size, unsigned mode)
|
||||
: handle() {
|
||||
acquire(name, size, mode);
|
||||
}
|
||||
|
||||
handle::handle(handle&& rhs)
|
||||
: handle() {
|
||||
swap(rhs);
|
||||
}
|
||||
|
||||
handle::~handle() {
|
||||
release();
|
||||
p_->clear();
|
||||
}
|
||||
|
||||
void handle::swap(handle& rhs) {
|
||||
std::swap(p_, rhs.p_);
|
||||
}
|
||||
|
||||
handle& handle::operator=(handle rhs) {
|
||||
swap(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool handle::valid() const {
|
||||
return impl(p_)->m_ != nullptr;
|
||||
}
|
||||
|
||||
std::size_t handle::size() const {
|
||||
return impl(p_)->s_;
|
||||
}
|
||||
|
||||
char const * handle::name() const {
|
||||
return impl(p_)->n_.c_str();
|
||||
}
|
||||
|
||||
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);
|
||||
impl(p_)->m_ = shm::get_mem(impl(p_)->id_, &(impl(p_)->s_));
|
||||
return valid();
|
||||
}
|
||||
|
||||
void handle::release() {
|
||||
if (impl(p_)->id_ == nullptr) return;
|
||||
shm::release(detach());
|
||||
}
|
||||
|
||||
void* handle::get() const {
|
||||
return impl(p_)->m_;
|
||||
}
|
||||
|
||||
void handle::attach(id_t id) {
|
||||
if (id == nullptr) return;
|
||||
release();
|
||||
impl(p_)->id_ = id;
|
||||
impl(p_)->m_ = shm::get_mem(impl(p_)->id_, &(impl(p_)->s_));
|
||||
}
|
||||
|
||||
id_t handle::detach() {
|
||||
auto old = impl(p_)->id_;
|
||||
impl(p_)->id_ = nullptr;
|
||||
impl(p_)->m_ = nullptr;
|
||||
impl(p_)->s_ = 0;
|
||||
impl(p_)->n_.clear();
|
||||
return old;
|
||||
}
|
||||
|
||||
} // namespace shm
|
||||
} // namespace ipc
|
||||
|
||||
152
src/waiter.cpp
152
src/waiter.cpp
@ -1,76 +1,76 @@
|
||||
#include "waiter.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "pimpl.h"
|
||||
#include "platform/waiter_wrapper.h"
|
||||
|
||||
#undef IPC_PP_CAT_
|
||||
#undef IPC_PP_JOIN_T__
|
||||
#undef IPC_PP_JOIN_
|
||||
|
||||
#define IPC_PP_CAT_(X, ...) X##__VA_ARGS__
|
||||
#define IPC_PP_JOIN_T__(X, ...) IPC_PP_CAT_(X, __VA_ARGS__)
|
||||
#define IPC_PP_JOIN_(X, ...) IPC_PP_JOIN_T__(X, __VA_ARGS__)
|
||||
|
||||
namespace ipc {
|
||||
|
||||
#undef IPC_OBJECT_TYPE_
|
||||
#undef IPC_OBJECT_TYPE_OPEN_PARS_
|
||||
#undef IPC_OBJECT_TYPE_OPEN_ARGS_
|
||||
|
||||
#define IPC_OBJECT_TYPE_ mutex
|
||||
#define IPC_OBJECT_TYPE_OPEN_PARS_
|
||||
#define IPC_OBJECT_TYPE_OPEN_ARGS_
|
||||
|
||||
#include "waiter_template.inc"
|
||||
|
||||
bool mutex::lock() {
|
||||
return impl(p_)->h_.lock();
|
||||
}
|
||||
|
||||
bool mutex::unlock() {
|
||||
return impl(p_)->h_.unlock();
|
||||
}
|
||||
|
||||
#undef IPC_OBJECT_TYPE_
|
||||
#undef IPC_OBJECT_TYPE_OPEN_PARS_
|
||||
#undef IPC_OBJECT_TYPE_OPEN_ARGS_
|
||||
|
||||
#define IPC_OBJECT_TYPE_ semaphore
|
||||
#define IPC_OBJECT_TYPE_OPEN_PARS_ , long count
|
||||
#define IPC_OBJECT_TYPE_OPEN_ARGS_ , count
|
||||
|
||||
#include "waiter_template.inc"
|
||||
|
||||
bool semaphore::wait(std::size_t tm) {
|
||||
return impl(p_)->h_.wait(tm);
|
||||
}
|
||||
|
||||
bool semaphore::post(long count) {
|
||||
return impl(p_)->h_.post(count);
|
||||
}
|
||||
|
||||
#undef IPC_OBJECT_TYPE_
|
||||
#undef IPC_OBJECT_TYPE_OPEN_PARS_
|
||||
#undef IPC_OBJECT_TYPE_OPEN_ARGS_
|
||||
|
||||
#define IPC_OBJECT_TYPE_ condition
|
||||
#define IPC_OBJECT_TYPE_OPEN_PARS_
|
||||
#define IPC_OBJECT_TYPE_OPEN_ARGS_
|
||||
|
||||
#include "waiter_template.inc"
|
||||
|
||||
bool condition::wait(mutex& mtx, std::size_t tm) {
|
||||
return impl(p_)->h_.wait(impl(mtx.p_)->h_, tm);
|
||||
}
|
||||
|
||||
bool condition::notify() {
|
||||
return impl(p_)->h_.notify();
|
||||
}
|
||||
|
||||
bool condition::broadcast() {
|
||||
return impl(p_)->h_.broadcast();
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
#include "waiter.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "pimpl.h"
|
||||
#include "platform/waiter_wrapper.h"
|
||||
|
||||
#undef IPC_PP_CAT_
|
||||
#undef IPC_PP_JOIN_T__
|
||||
#undef IPC_PP_JOIN_
|
||||
|
||||
#define IPC_PP_CAT_(X, ...) X##__VA_ARGS__
|
||||
#define IPC_PP_JOIN_T__(X, ...) IPC_PP_CAT_(X, __VA_ARGS__)
|
||||
#define IPC_PP_JOIN_(X, ...) IPC_PP_JOIN_T__(X, __VA_ARGS__)
|
||||
|
||||
namespace ipc {
|
||||
|
||||
#undef IPC_OBJECT_TYPE_
|
||||
#undef IPC_OBJECT_TYPE_OPEN_PARS_
|
||||
#undef IPC_OBJECT_TYPE_OPEN_ARGS_
|
||||
|
||||
#define IPC_OBJECT_TYPE_ mutex
|
||||
#define IPC_OBJECT_TYPE_OPEN_PARS_
|
||||
#define IPC_OBJECT_TYPE_OPEN_ARGS_
|
||||
|
||||
#include "waiter_template.inc"
|
||||
|
||||
bool mutex::lock() {
|
||||
return impl(p_)->h_.lock();
|
||||
}
|
||||
|
||||
bool mutex::unlock() {
|
||||
return impl(p_)->h_.unlock();
|
||||
}
|
||||
|
||||
#undef IPC_OBJECT_TYPE_
|
||||
#undef IPC_OBJECT_TYPE_OPEN_PARS_
|
||||
#undef IPC_OBJECT_TYPE_OPEN_ARGS_
|
||||
|
||||
#define IPC_OBJECT_TYPE_ semaphore
|
||||
#define IPC_OBJECT_TYPE_OPEN_PARS_ , long count
|
||||
#define IPC_OBJECT_TYPE_OPEN_ARGS_ , count
|
||||
|
||||
#include "waiter_template.inc"
|
||||
|
||||
bool semaphore::wait(std::size_t tm) {
|
||||
return impl(p_)->h_.wait(tm);
|
||||
}
|
||||
|
||||
bool semaphore::post(long count) {
|
||||
return impl(p_)->h_.post(count);
|
||||
}
|
||||
|
||||
#undef IPC_OBJECT_TYPE_
|
||||
#undef IPC_OBJECT_TYPE_OPEN_PARS_
|
||||
#undef IPC_OBJECT_TYPE_OPEN_ARGS_
|
||||
|
||||
#define IPC_OBJECT_TYPE_ condition
|
||||
#define IPC_OBJECT_TYPE_OPEN_PARS_
|
||||
#define IPC_OBJECT_TYPE_OPEN_ARGS_
|
||||
|
||||
#include "waiter_template.inc"
|
||||
|
||||
bool condition::wait(mutex& mtx, std::size_t tm) {
|
||||
return impl(p_)->h_.wait(impl(mtx.p_)->h_, tm);
|
||||
}
|
||||
|
||||
bool condition::notify() {
|
||||
return impl(p_)->h_.notify();
|
||||
}
|
||||
|
||||
bool condition::broadcast() {
|
||||
return impl(p_)->h_.broadcast();
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
@ -1,43 +1,43 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QObject>
|
||||
#include <QVector>
|
||||
#include <QString>
|
||||
#include <QDebug>
|
||||
|
||||
#include "test.h"
|
||||
|
||||
namespace {
|
||||
|
||||
QVector<QObject*>* suites__ = nullptr;
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
TestSuite::TestSuite() {
|
||||
static struct __ {
|
||||
QVector<QObject*> suites_;
|
||||
__() { suites__ = &suites_; }
|
||||
} _;
|
||||
_.suites_ << this;
|
||||
}
|
||||
|
||||
const char* TestSuite::name() const {
|
||||
return "";
|
||||
}
|
||||
|
||||
void TestSuite::initTestCase() {
|
||||
qDebug() << QString("#### Start: %1 ####").arg(name());
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
QCoreApplication app(argc, argv);
|
||||
Q_UNUSED(app)
|
||||
|
||||
// QThread::sleep(5);
|
||||
|
||||
int failed_count = 0;
|
||||
for (const auto& suite : (*suites__)) {
|
||||
if (QTest::qExec(suite, argc, argv) != 0)
|
||||
++failed_count;
|
||||
}
|
||||
return failed_count;
|
||||
}
|
||||
#include <QCoreApplication>
|
||||
#include <QObject>
|
||||
#include <QVector>
|
||||
#include <QString>
|
||||
#include <QDebug>
|
||||
|
||||
#include "test.h"
|
||||
|
||||
namespace {
|
||||
|
||||
QVector<QObject*>* suites__ = nullptr;
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
TestSuite::TestSuite() {
|
||||
static struct __ {
|
||||
QVector<QObject*> suites_;
|
||||
__() { suites__ = &suites_; }
|
||||
} _;
|
||||
_.suites_ << this;
|
||||
}
|
||||
|
||||
const char* TestSuite::name() const {
|
||||
return "";
|
||||
}
|
||||
|
||||
void TestSuite::initTestCase() {
|
||||
qDebug() << QString("#### Start: %1 ####").arg(name());
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
QCoreApplication app(argc, argv);
|
||||
Q_UNUSED(app)
|
||||
|
||||
// QThread::sleep(5);
|
||||
|
||||
int failed_count = 0;
|
||||
for (const auto& suite : (*suites__)) {
|
||||
if (QTest::qExec(suite, argc, argv) != 0)
|
||||
++failed_count;
|
||||
}
|
||||
return failed_count;
|
||||
}
|
||||
|
||||
310
test/test.h
310
test/test.h
@ -1,155 +1,155 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtTest>
|
||||
|
||||
#include <iostream>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#if defined(__GNUC__)
|
||||
# include <cxxabi.h> // abi::__cxa_demangle
|
||||
#endif/*__GNUC__*/
|
||||
|
||||
#include "stopwatch.hpp"
|
||||
#include "spin_lock.hpp"
|
||||
|
||||
class TestSuite : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TestSuite();
|
||||
|
||||
protected:
|
||||
virtual const char* name() const;
|
||||
|
||||
protected slots:
|
||||
virtual void initTestCase();
|
||||
};
|
||||
|
||||
struct test_stopwatch {
|
||||
capo::stopwatch<> sw_;
|
||||
std::atomic_flag started_ = ATOMIC_FLAG_INIT;
|
||||
|
||||
void start() {
|
||||
if (!started_.test_and_set()) {
|
||||
sw_.start();
|
||||
}
|
||||
}
|
||||
|
||||
template <int Factor>
|
||||
void print_elapsed(int N, int M, int Loops) {
|
||||
auto ts = sw_.elapsed<std::chrono::microseconds>();
|
||||
std::cout << "[" << N << ":" << M << ", " << Loops << "] "
|
||||
<< "performance: " << (ts / 1000.0) << " ms, "
|
||||
<< (double(ts) / double(Factor ? (Loops * Factor) : (Loops * N))) << " us/d" << std::endl;
|
||||
}
|
||||
|
||||
void print_elapsed(int N, int M, int Loops) {
|
||||
print_elapsed<0>(N, M, Loops);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename V>
|
||||
struct test_verify;
|
||||
|
||||
template <>
|
||||
struct test_verify<void> {
|
||||
test_verify (int) {}
|
||||
void prepare (void*) {}
|
||||
void verify (int, int) {}
|
||||
|
||||
template <typename U>
|
||||
void push_data(int, U&&) {}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct test_cq;
|
||||
|
||||
template <typename T>
|
||||
std::string type_name() {
|
||||
#if defined(__GNUC__)
|
||||
const char* typeid_name = typeid(T).name();
|
||||
const char* real_name = abi::__cxa_demangle(typeid_name, nullptr, nullptr, nullptr);
|
||||
std::unique_ptr<void, decltype(::free)*> guard { (void*)real_name, ::free };
|
||||
if (real_name == nullptr) real_name = typeid_name;
|
||||
return real_name;
|
||||
#else
|
||||
return typeid(T).name();
|
||||
#endif/*__GNUC__*/
|
||||
}
|
||||
|
||||
template <int N, int M, int Loops, typename V = void, typename T>
|
||||
void benchmark_prod_cons(T* cq) {
|
||||
std::cout << "benchmark_prod_cons " << type_name<T>() << " [" << N << ":" << M << ", " << Loops << "]" << std::endl;
|
||||
test_cq<T> tcq { cq };
|
||||
|
||||
std::thread producers[N];
|
||||
std::thread consumers[M];
|
||||
std::atomic_int fini_p { 0 }, fini_c { 0 };
|
||||
|
||||
test_stopwatch sw;
|
||||
test_verify<V> vf { M };
|
||||
|
||||
// capo::spin_lock lc;
|
||||
|
||||
int cid = 0;
|
||||
for (auto& t : consumers) {
|
||||
t = std::thread{[&, cid] {
|
||||
vf.prepare(&t);
|
||||
auto cn = tcq.connect();
|
||||
int i = 0;
|
||||
tcq.recv(cn, [&](auto&& msg) {
|
||||
// if (i % ((Loops * N) / 10) == 0) {
|
||||
// std::unique_lock<capo::spin_lock> guard { lc };
|
||||
// std::cout << cid << "-recving: " << (i * 100) / (Loops * N) << "%" << std::endl;
|
||||
// }
|
||||
vf.push_data(cid, msg);
|
||||
++i;
|
||||
});
|
||||
// {
|
||||
// std::unique_lock<capo::spin_lock> guard { lc };
|
||||
// std::cout << cid << "-consumer-disconnect" << std::endl;
|
||||
// }
|
||||
tcq.disconnect(cn);
|
||||
if ((fini_c.fetch_add(1, std::memory_order_relaxed) + 1) != M) {
|
||||
// std::unique_lock<capo::spin_lock> guard { lc };
|
||||
// std::cout << cid << "-consumer-end" << std::endl;
|
||||
return;
|
||||
}
|
||||
sw.print_elapsed(N, M, Loops);
|
||||
vf.verify(N, Loops);
|
||||
}};
|
||||
++cid;
|
||||
}
|
||||
|
||||
tcq.wait_start(M);
|
||||
|
||||
std::cout << "start producers..." << std::endl;
|
||||
int pid = 0;
|
||||
for (auto& t : producers) {
|
||||
t = std::thread{[&, pid] {
|
||||
auto cn = tcq.connect_send();
|
||||
sw.start();
|
||||
for (int i = 0; i < Loops; ++i) {
|
||||
// if (i % (Loops / 10) == 0) {
|
||||
// std::unique_lock<capo::spin_lock> guard { lc };
|
||||
// std::cout << pid << "-sending: " << (i * 100 / Loops) << "%" << std::endl;
|
||||
// }
|
||||
tcq.send(cn, { pid, i });
|
||||
}
|
||||
if ((fini_p.fetch_add(1, std::memory_order_relaxed) + 1) != N) {
|
||||
return;
|
||||
}
|
||||
// quit
|
||||
tcq.send(cn, { -1, -1 });
|
||||
tcq.disconnect(cn);
|
||||
}};
|
||||
++pid;
|
||||
}
|
||||
for (auto& t : producers) t.join();
|
||||
for (auto& t : consumers) t.join();
|
||||
}
|
||||
#pragma once
|
||||
|
||||
#include <QtTest>
|
||||
|
||||
#include <iostream>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#if defined(__GNUC__)
|
||||
# include <cxxabi.h> // abi::__cxa_demangle
|
||||
#endif/*__GNUC__*/
|
||||
|
||||
#include "stopwatch.hpp"
|
||||
#include "spin_lock.hpp"
|
||||
|
||||
class TestSuite : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TestSuite();
|
||||
|
||||
protected:
|
||||
virtual const char* name() const;
|
||||
|
||||
protected slots:
|
||||
virtual void initTestCase();
|
||||
};
|
||||
|
||||
struct test_stopwatch {
|
||||
capo::stopwatch<> sw_;
|
||||
std::atomic_flag started_ = ATOMIC_FLAG_INIT;
|
||||
|
||||
void start() {
|
||||
if (!started_.test_and_set()) {
|
||||
sw_.start();
|
||||
}
|
||||
}
|
||||
|
||||
template <int Factor>
|
||||
void print_elapsed(int N, int M, int Loops) {
|
||||
auto ts = sw_.elapsed<std::chrono::microseconds>();
|
||||
std::cout << "[" << N << ":" << M << ", " << Loops << "] "
|
||||
<< "performance: " << (ts / 1000.0) << " ms, "
|
||||
<< (double(ts) / double(Factor ? (Loops * Factor) : (Loops * N))) << " us/d" << std::endl;
|
||||
}
|
||||
|
||||
void print_elapsed(int N, int M, int Loops) {
|
||||
print_elapsed<0>(N, M, Loops);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename V>
|
||||
struct test_verify;
|
||||
|
||||
template <>
|
||||
struct test_verify<void> {
|
||||
test_verify (int) {}
|
||||
void prepare (void*) {}
|
||||
void verify (int, int) {}
|
||||
|
||||
template <typename U>
|
||||
void push_data(int, U&&) {}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct test_cq;
|
||||
|
||||
template <typename T>
|
||||
std::string type_name() {
|
||||
#if defined(__GNUC__)
|
||||
const char* typeid_name = typeid(T).name();
|
||||
const char* real_name = abi::__cxa_demangle(typeid_name, nullptr, nullptr, nullptr);
|
||||
std::unique_ptr<void, decltype(::free)*> guard { (void*)real_name, ::free };
|
||||
if (real_name == nullptr) real_name = typeid_name;
|
||||
return real_name;
|
||||
#else
|
||||
return typeid(T).name();
|
||||
#endif/*__GNUC__*/
|
||||
}
|
||||
|
||||
template <int N, int M, int Loops, typename V = void, typename T>
|
||||
void benchmark_prod_cons(T* cq) {
|
||||
std::cout << "benchmark_prod_cons " << type_name<T>() << " [" << N << ":" << M << ", " << Loops << "]" << std::endl;
|
||||
test_cq<T> tcq { cq };
|
||||
|
||||
std::thread producers[N];
|
||||
std::thread consumers[M];
|
||||
std::atomic_int fini_p { 0 }, fini_c { 0 };
|
||||
|
||||
test_stopwatch sw;
|
||||
test_verify<V> vf { M };
|
||||
|
||||
// capo::spin_lock lc;
|
||||
|
||||
int cid = 0;
|
||||
for (auto& t : consumers) {
|
||||
t = std::thread{[&, cid] {
|
||||
vf.prepare(&t);
|
||||
auto cn = tcq.connect();
|
||||
int i = 0;
|
||||
tcq.recv(cn, [&](auto&& msg) {
|
||||
// if (i % ((Loops * N) / 10) == 0) {
|
||||
// std::unique_lock<capo::spin_lock> guard { lc };
|
||||
// std::cout << cid << "-recving: " << (i * 100) / (Loops * N) << "%" << std::endl;
|
||||
// }
|
||||
vf.push_data(cid, msg);
|
||||
++i;
|
||||
});
|
||||
// {
|
||||
// std::unique_lock<capo::spin_lock> guard { lc };
|
||||
// std::cout << cid << "-consumer-disconnect" << std::endl;
|
||||
// }
|
||||
tcq.disconnect(cn);
|
||||
if ((fini_c.fetch_add(1, std::memory_order_relaxed) + 1) != M) {
|
||||
// std::unique_lock<capo::spin_lock> guard { lc };
|
||||
// std::cout << cid << "-consumer-end" << std::endl;
|
||||
return;
|
||||
}
|
||||
sw.print_elapsed(N, M, Loops);
|
||||
vf.verify(N, Loops);
|
||||
}};
|
||||
++cid;
|
||||
}
|
||||
|
||||
tcq.wait_start(M);
|
||||
|
||||
std::cout << "start producers..." << std::endl;
|
||||
int pid = 0;
|
||||
for (auto& t : producers) {
|
||||
t = std::thread{[&, pid] {
|
||||
auto cn = tcq.connect_send();
|
||||
sw.start();
|
||||
for (int i = 0; i < Loops; ++i) {
|
||||
// if (i % (Loops / 10) == 0) {
|
||||
// std::unique_lock<capo::spin_lock> guard { lc };
|
||||
// std::cout << pid << "-sending: " << (i * 100 / Loops) << "%" << std::endl;
|
||||
// }
|
||||
tcq.send(cn, { pid, i });
|
||||
}
|
||||
if ((fini_p.fetch_add(1, std::memory_order_relaxed) + 1) != N) {
|
||||
return;
|
||||
}
|
||||
// quit
|
||||
tcq.send(cn, { -1, -1 });
|
||||
tcq.disconnect(cn);
|
||||
}};
|
||||
++pid;
|
||||
}
|
||||
for (auto& t : producers) t.join();
|
||||
for (auto& t : consumers) t.join();
|
||||
}
|
||||
|
||||
@ -1,400 +1,400 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "queue.h"
|
||||
#include "prod_cons.h"
|
||||
#include "policy.h"
|
||||
#include "circ/elem_array.h"
|
||||
#include "memory/resource.h"
|
||||
#include "test.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct msg_t {
|
||||
int pid_;
|
||||
int dat_;
|
||||
};
|
||||
|
||||
template <ipc::relat Rp, ipc::relat Rc, ipc::trans Ts>
|
||||
using pc_t = ipc::prod_cons_impl<ipc::wr<Rp, Rc, Ts>>;
|
||||
|
||||
template <std::size_t DataSize, typename Policy>
|
||||
struct ea_t : public ipc::circ::elem_array<Policy, DataSize, 1> {
|
||||
ea_t() { std::memset(this, 0, sizeof(ipc::circ::elem_array<Policy, DataSize, 1>)); }
|
||||
};
|
||||
|
||||
using cq_t = ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::single, ipc::relat::multi, ipc::trans::broadcast>
|
||||
>;
|
||||
cq_t* cq__;
|
||||
|
||||
bool operator==(msg_t const & m1, msg_t const & m2) {
|
||||
return (m1.pid_ == m2.pid_) && (m1.dat_ == m2.dat_);
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
template <std::size_t D, typename P>
|
||||
struct test_verify<ea_t<D, P>> {
|
||||
std::vector<std::unordered_map<int, std::vector<int>>> list_;
|
||||
|
||||
test_verify(int M)
|
||||
: list_(static_cast<std::size_t>(M))
|
||||
{}
|
||||
|
||||
void prepare(void* pt) {
|
||||
std::cout << "start consumer: " << pt << std::endl;
|
||||
}
|
||||
|
||||
void push_data(int cid, msg_t const & msg) {
|
||||
list_[cid][msg.pid_].push_back(msg.dat_);
|
||||
}
|
||||
|
||||
void verify(int N, int Loops) {
|
||||
std::cout << "verifying..." << std::endl;
|
||||
for (auto& c_dats : list_) {
|
||||
for (int n = 0; n < N; ++n) {
|
||||
auto& vec = c_dats[n];
|
||||
//for (int d : vec) {
|
||||
// std::cout << d << " ";
|
||||
//}
|
||||
//std::cout << std::endl;
|
||||
QCOMPARE(vec.size(), static_cast<std::size_t>(Loops));
|
||||
int i = 0;
|
||||
for (int d : vec) {
|
||||
QCOMPARE(i, d);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <ipc::relat Rp>
|
||||
struct test_verify<pc_t<Rp, ipc::relat::multi, ipc::trans::unicast>> : test_verify<cq_t> {
|
||||
using test_verify<cq_t>::test_verify;
|
||||
|
||||
void verify(int N, int Loops) {
|
||||
std::cout << "verifying..." << std::endl;
|
||||
for (int n = 0; n < N; ++n) {
|
||||
std::vector<int> datas;
|
||||
std::uint64_t sum = 0;
|
||||
for (auto& c_dats : list_) {
|
||||
for (int d : c_dats[n]) {
|
||||
datas.push_back(d);
|
||||
sum += d;
|
||||
}
|
||||
}
|
||||
QCOMPARE(datas.size(), static_cast<std::size_t>(Loops));
|
||||
QCOMPARE(sum, (Loops * std::uint64_t(Loops - 1)) / 2);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename P>
|
||||
struct quit_mode;
|
||||
|
||||
template <ipc::relat Rp, ipc::relat Rc>
|
||||
struct quit_mode<pc_t<Rp, Rc, ipc::trans::unicast>> {
|
||||
using type = volatile bool;
|
||||
};
|
||||
|
||||
template <ipc::relat Rp, ipc::relat Rc>
|
||||
struct quit_mode<pc_t<Rp, Rc, ipc::trans::broadcast>> {
|
||||
struct type {
|
||||
constexpr type(bool) {}
|
||||
constexpr operator bool() const { return false; }
|
||||
};
|
||||
};
|
||||
|
||||
template <std::size_t D, typename P>
|
||||
struct test_cq<ea_t<D, P>> {
|
||||
using ca_t = ea_t<D, P>;
|
||||
using cn_t = decltype(std::declval<ca_t>().cursor());
|
||||
|
||||
typename quit_mode<P>::type quit_ = false;
|
||||
ca_t* ca_;
|
||||
|
||||
test_cq(ca_t* ca) : ca_(ca) {}
|
||||
|
||||
cn_t connect() {
|
||||
auto cur = ca_->cursor();
|
||||
ca_->connect();
|
||||
return cur;
|
||||
}
|
||||
|
||||
void disconnect(cn_t) {
|
||||
ca_->disconnect();
|
||||
}
|
||||
|
||||
void disconnect(ca_t*) {
|
||||
}
|
||||
|
||||
void wait_start(int M) {
|
||||
while (ca_->conn_count() != static_cast<std::size_t>(M)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void recv(cn_t cur, F&& proc) {
|
||||
while (1) {
|
||||
msg_t msg;
|
||||
while (ca_->pop(&cur, [&msg](void* p) {
|
||||
msg = *static_cast<msg_t*>(p);
|
||||
})) {
|
||||
if (msg.pid_ < 0) {
|
||||
quit_ = true;
|
||||
return;
|
||||
}
|
||||
proc(msg);
|
||||
}
|
||||
if (quit_) return;
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
|
||||
ca_t* connect_send() {
|
||||
return ca_;
|
||||
}
|
||||
|
||||
void send(ca_t* ca, msg_t const & msg) {
|
||||
while (!ca->push([&msg](void* p) {
|
||||
(*static_cast<msg_t*>(p)) = msg;
|
||||
})) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... T>
|
||||
struct test_cq<ipc::queue<T...>> {
|
||||
using cn_t = ipc::queue<T...>;
|
||||
|
||||
test_cq(void*) {}
|
||||
|
||||
cn_t* connect() {
|
||||
cn_t* queue = new cn_t { "test-ipc-queue" };
|
||||
[&] { QVERIFY(queue->connect()); } ();
|
||||
return queue;
|
||||
}
|
||||
|
||||
void disconnect(cn_t* queue) {
|
||||
queue->disconnect();
|
||||
delete queue;
|
||||
}
|
||||
|
||||
void wait_start(int M) {
|
||||
cn_t que("test-ipc-queue");
|
||||
while (que.conn_count() != static_cast<std::size_t>(M)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void recv(cn_t* queue, F&& proc) {
|
||||
while(1) {
|
||||
typename cn_t::value_t msg;
|
||||
while (!queue->pop(msg)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
if (msg.pid_ < 0) return;
|
||||
proc(msg);
|
||||
}
|
||||
}
|
||||
|
||||
cn_t* connect_send() {
|
||||
return new cn_t { "test-ipc-queue" };
|
||||
}
|
||||
|
||||
void send(cn_t* cn, msg_t const & msg) {
|
||||
while (!cn->push(msg)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
class Unit : public TestSuite {
|
||||
Q_OBJECT
|
||||
|
||||
const char* name() const {
|
||||
return "test_circ";
|
||||
}
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
|
||||
void test_inst();
|
||||
void test_prod_cons_1v1();
|
||||
void test_prod_cons_1v3();
|
||||
void test_prod_cons_performance();
|
||||
void test_queue();
|
||||
} /*unit__*/;
|
||||
|
||||
#include "test_circ.moc"
|
||||
|
||||
constexpr int LoopCount = 1000000;
|
||||
//constexpr int LoopCount = 1000/*0000*/;
|
||||
|
||||
void Unit::initTestCase() {
|
||||
TestSuite::initTestCase();
|
||||
cq__ = new cq_t;
|
||||
}
|
||||
|
||||
void Unit::cleanupTestCase() {
|
||||
delete cq__;
|
||||
}
|
||||
|
||||
void Unit::test_inst() {
|
||||
std::cout << "cq_t::head_size = " << cq_t::head_size << std::endl;
|
||||
std::cout << "cq_t::data_size = " << cq_t::data_size << std::endl;
|
||||
std::cout << "cq_t::elem_size = " << cq_t::elem_size << std::endl;
|
||||
std::cout << "cq_t::block_size = " << cq_t::block_size << std::endl;
|
||||
|
||||
QCOMPARE(static_cast<std::size_t>(cq_t::data_size), sizeof(msg_t));
|
||||
|
||||
std::cout << "sizeof(ea_t<sizeof(msg_t)>) = " << sizeof(*cq__) << std::endl;
|
||||
}
|
||||
|
||||
template <int N, int M, bool V = true, int Loops = LoopCount>
|
||||
void test_prod_cons() {
|
||||
benchmark_prod_cons<N, M, Loops, std::conditional_t<V, cq_t, void>>(cq__);
|
||||
}
|
||||
|
||||
void Unit::test_prod_cons_1v1() {
|
||||
// ea_t<
|
||||
// sizeof(msg_t),
|
||||
// pc_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast>
|
||||
// > el_arr_mmb;
|
||||
// benchmark_prod_cons<1, 1, LoopCount, void>(&el_arr_mmb);
|
||||
// benchmark_prod_cons<2, 1, LoopCount, void>(&el_arr_mmb);
|
||||
|
||||
ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast>
|
||||
> el_arr_ssu;
|
||||
benchmark_prod_cons<1, 1, LoopCount, cq_t>(&el_arr_ssu);
|
||||
benchmark_prod_cons<1, 1, LoopCount, void>(&el_arr_ssu);
|
||||
|
||||
ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::single, ipc::relat::multi, ipc::trans::unicast>
|
||||
> el_arr_smu;
|
||||
benchmark_prod_cons<1, 1, LoopCount, decltype(el_arr_smu)::policy_t>(&el_arr_smu);
|
||||
benchmark_prod_cons<1, 1, LoopCount, void>(&el_arr_smu);
|
||||
|
||||
ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::unicast>
|
||||
> el_arr_mmu;
|
||||
benchmark_prod_cons<1, 1, LoopCount, decltype(el_arr_mmu)::policy_t>(&el_arr_mmu);
|
||||
benchmark_prod_cons<1, 1, LoopCount, void>(&el_arr_mmu);
|
||||
|
||||
test_prod_cons<1, 1>();
|
||||
test_prod_cons<1, 1, false>();
|
||||
|
||||
ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast>
|
||||
> el_arr_mmb;
|
||||
benchmark_prod_cons<1, 1, LoopCount, cq_t>(&el_arr_mmb);
|
||||
benchmark_prod_cons<1, 1, LoopCount, void>(&el_arr_mmb);
|
||||
}
|
||||
|
||||
void Unit::test_prod_cons_1v3() {
|
||||
ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::single, ipc::relat::multi, ipc::trans::unicast>
|
||||
> el_arr_smu;
|
||||
benchmark_prod_cons<1, 3, LoopCount, decltype(el_arr_smu)::policy_t>(&el_arr_smu);
|
||||
benchmark_prod_cons<1, 3, LoopCount, void>(&el_arr_smu);
|
||||
|
||||
ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::unicast>
|
||||
> el_arr_mmu;
|
||||
benchmark_prod_cons<1, 3, LoopCount, decltype(el_arr_mmu)::policy_t>(&el_arr_mmu);
|
||||
benchmark_prod_cons<1, 3, LoopCount, void>(&el_arr_mmu);
|
||||
|
||||
test_prod_cons<1, 3>();
|
||||
test_prod_cons<1, 3, false>();
|
||||
|
||||
ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast>
|
||||
> el_arr_mmb;
|
||||
benchmark_prod_cons<1, 3, LoopCount, cq_t>(&el_arr_mmb);
|
||||
benchmark_prod_cons<1, 3, LoopCount, void>(&el_arr_mmb);
|
||||
}
|
||||
|
||||
void Unit::test_prod_cons_performance() {
|
||||
ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::single, ipc::relat::multi, ipc::trans::unicast>
|
||||
> el_arr_smu;
|
||||
ipc::detail::static_for<8>([&el_arr_smu](auto index) {
|
||||
benchmark_prod_cons<1, decltype(index)::value + 1, LoopCount, void>(&el_arr_smu);
|
||||
});
|
||||
|
||||
ipc::detail::static_for<8>([](auto index) {
|
||||
test_prod_cons<1, decltype(index)::value + 1, false>();
|
||||
});
|
||||
test_prod_cons<1, 8>(); // test & verify
|
||||
|
||||
ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::unicast>
|
||||
> el_arr_mmu;
|
||||
ipc::detail::static_for<8>([&el_arr_mmu](auto index) {
|
||||
benchmark_prod_cons<1, decltype(index)::value + 1, LoopCount, void>(&el_arr_mmu);
|
||||
});
|
||||
ipc::detail::static_for<8>([&el_arr_mmu](auto index) {
|
||||
benchmark_prod_cons<decltype(index)::value + 1, 1, LoopCount, void>(&el_arr_mmu);
|
||||
});
|
||||
ipc::detail::static_for<8>([&el_arr_mmu](auto index) {
|
||||
benchmark_prod_cons<decltype(index)::value + 1, decltype(index)::value + 1, LoopCount, void>(&el_arr_mmu);
|
||||
});
|
||||
|
||||
ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast>
|
||||
> el_arr_mmb;
|
||||
ipc::detail::static_for<8>([&el_arr_mmb](auto index) {
|
||||
benchmark_prod_cons<1, decltype(index)::value + 1, LoopCount, void>(&el_arr_mmb);
|
||||
});
|
||||
ipc::detail::static_for<8>([&el_arr_mmb](auto index) {
|
||||
benchmark_prod_cons<decltype(index)::value + 1, 1, LoopCount, void>(&el_arr_mmb);
|
||||
});
|
||||
ipc::detail::static_for<8>([&el_arr_mmb](auto index) {
|
||||
benchmark_prod_cons<decltype(index)::value + 1, decltype(index)::value + 1, LoopCount, void>(&el_arr_mmb);
|
||||
});
|
||||
}
|
||||
|
||||
void Unit::test_queue() {
|
||||
using queue_t = ipc::queue<msg_t, ipc::policy::choose<
|
||||
ipc::circ::elem_array,
|
||||
ipc::wr<ipc::relat::single, ipc::relat::multi, ipc::trans::broadcast>
|
||||
>>;
|
||||
queue_t queue;
|
||||
|
||||
QVERIFY(!queue.push(msg_t { 1, 2 }));
|
||||
msg_t msg {};
|
||||
QVERIFY(!queue.pop(msg));
|
||||
QCOMPARE(msg, (msg_t {}));
|
||||
QVERIFY(sizeof(decltype(queue)::elems_t) <= sizeof(*cq__));
|
||||
|
||||
ipc::detail::static_for<16>([](auto index) {
|
||||
benchmark_prod_cons<1, decltype(index)::value + 1, LoopCount>((queue_t*)nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "queue.h"
|
||||
#include "prod_cons.h"
|
||||
#include "policy.h"
|
||||
#include "circ/elem_array.h"
|
||||
#include "memory/resource.h"
|
||||
#include "test.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct msg_t {
|
||||
int pid_;
|
||||
int dat_;
|
||||
};
|
||||
|
||||
template <ipc::relat Rp, ipc::relat Rc, ipc::trans Ts>
|
||||
using pc_t = ipc::prod_cons_impl<ipc::wr<Rp, Rc, Ts>>;
|
||||
|
||||
template <std::size_t DataSize, typename Policy>
|
||||
struct ea_t : public ipc::circ::elem_array<Policy, DataSize, 1> {
|
||||
ea_t() { std::memset(this, 0, sizeof(ipc::circ::elem_array<Policy, DataSize, 1>)); }
|
||||
};
|
||||
|
||||
using cq_t = ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::single, ipc::relat::multi, ipc::trans::broadcast>
|
||||
>;
|
||||
cq_t* cq__;
|
||||
|
||||
bool operator==(msg_t const & m1, msg_t const & m2) {
|
||||
return (m1.pid_ == m2.pid_) && (m1.dat_ == m2.dat_);
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
template <std::size_t D, typename P>
|
||||
struct test_verify<ea_t<D, P>> {
|
||||
std::vector<std::unordered_map<int, std::vector<int>>> list_;
|
||||
|
||||
test_verify(int M)
|
||||
: list_(static_cast<std::size_t>(M))
|
||||
{}
|
||||
|
||||
void prepare(void* pt) {
|
||||
std::cout << "start consumer: " << pt << std::endl;
|
||||
}
|
||||
|
||||
void push_data(int cid, msg_t const & msg) {
|
||||
list_[cid][msg.pid_].push_back(msg.dat_);
|
||||
}
|
||||
|
||||
void verify(int N, int Loops) {
|
||||
std::cout << "verifying..." << std::endl;
|
||||
for (auto& c_dats : list_) {
|
||||
for (int n = 0; n < N; ++n) {
|
||||
auto& vec = c_dats[n];
|
||||
//for (int d : vec) {
|
||||
// std::cout << d << " ";
|
||||
//}
|
||||
//std::cout << std::endl;
|
||||
QCOMPARE(vec.size(), static_cast<std::size_t>(Loops));
|
||||
int i = 0;
|
||||
for (int d : vec) {
|
||||
QCOMPARE(i, d);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <ipc::relat Rp>
|
||||
struct test_verify<pc_t<Rp, ipc::relat::multi, ipc::trans::unicast>> : test_verify<cq_t> {
|
||||
using test_verify<cq_t>::test_verify;
|
||||
|
||||
void verify(int N, int Loops) {
|
||||
std::cout << "verifying..." << std::endl;
|
||||
for (int n = 0; n < N; ++n) {
|
||||
std::vector<int> datas;
|
||||
std::uint64_t sum = 0;
|
||||
for (auto& c_dats : list_) {
|
||||
for (int d : c_dats[n]) {
|
||||
datas.push_back(d);
|
||||
sum += d;
|
||||
}
|
||||
}
|
||||
QCOMPARE(datas.size(), static_cast<std::size_t>(Loops));
|
||||
QCOMPARE(sum, (Loops * std::uint64_t(Loops - 1)) / 2);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename P>
|
||||
struct quit_mode;
|
||||
|
||||
template <ipc::relat Rp, ipc::relat Rc>
|
||||
struct quit_mode<pc_t<Rp, Rc, ipc::trans::unicast>> {
|
||||
using type = volatile bool;
|
||||
};
|
||||
|
||||
template <ipc::relat Rp, ipc::relat Rc>
|
||||
struct quit_mode<pc_t<Rp, Rc, ipc::trans::broadcast>> {
|
||||
struct type {
|
||||
constexpr type(bool) {}
|
||||
constexpr operator bool() const { return false; }
|
||||
};
|
||||
};
|
||||
|
||||
template <std::size_t D, typename P>
|
||||
struct test_cq<ea_t<D, P>> {
|
||||
using ca_t = ea_t<D, P>;
|
||||
using cn_t = decltype(std::declval<ca_t>().cursor());
|
||||
|
||||
typename quit_mode<P>::type quit_ = false;
|
||||
ca_t* ca_;
|
||||
|
||||
test_cq(ca_t* ca) : ca_(ca) {}
|
||||
|
||||
cn_t connect() {
|
||||
auto cur = ca_->cursor();
|
||||
ca_->connect();
|
||||
return cur;
|
||||
}
|
||||
|
||||
void disconnect(cn_t) {
|
||||
ca_->disconnect();
|
||||
}
|
||||
|
||||
void disconnect(ca_t*) {
|
||||
}
|
||||
|
||||
void wait_start(int M) {
|
||||
while (ca_->conn_count() != static_cast<std::size_t>(M)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void recv(cn_t cur, F&& proc) {
|
||||
while (1) {
|
||||
msg_t msg;
|
||||
while (ca_->pop(&cur, [&msg](void* p) {
|
||||
msg = *static_cast<msg_t*>(p);
|
||||
})) {
|
||||
if (msg.pid_ < 0) {
|
||||
quit_ = true;
|
||||
return;
|
||||
}
|
||||
proc(msg);
|
||||
}
|
||||
if (quit_) return;
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
|
||||
ca_t* connect_send() {
|
||||
return ca_;
|
||||
}
|
||||
|
||||
void send(ca_t* ca, msg_t const & msg) {
|
||||
while (!ca->push([&msg](void* p) {
|
||||
(*static_cast<msg_t*>(p)) = msg;
|
||||
})) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... T>
|
||||
struct test_cq<ipc::queue<T...>> {
|
||||
using cn_t = ipc::queue<T...>;
|
||||
|
||||
test_cq(void*) {}
|
||||
|
||||
cn_t* connect() {
|
||||
cn_t* queue = new cn_t { "test-ipc-queue" };
|
||||
[&] { QVERIFY(queue->connect()); } ();
|
||||
return queue;
|
||||
}
|
||||
|
||||
void disconnect(cn_t* queue) {
|
||||
queue->disconnect();
|
||||
delete queue;
|
||||
}
|
||||
|
||||
void wait_start(int M) {
|
||||
cn_t que("test-ipc-queue");
|
||||
while (que.conn_count() != static_cast<std::size_t>(M)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void recv(cn_t* queue, F&& proc) {
|
||||
while(1) {
|
||||
typename cn_t::value_t msg;
|
||||
while (!queue->pop(msg)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
if (msg.pid_ < 0) return;
|
||||
proc(msg);
|
||||
}
|
||||
}
|
||||
|
||||
cn_t* connect_send() {
|
||||
return new cn_t { "test-ipc-queue" };
|
||||
}
|
||||
|
||||
void send(cn_t* cn, msg_t const & msg) {
|
||||
while (!cn->push(msg)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
class Unit : public TestSuite {
|
||||
Q_OBJECT
|
||||
|
||||
const char* name() const {
|
||||
return "test_circ";
|
||||
}
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
|
||||
void test_inst();
|
||||
void test_prod_cons_1v1();
|
||||
void test_prod_cons_1v3();
|
||||
void test_prod_cons_performance();
|
||||
void test_queue();
|
||||
} unit__;
|
||||
|
||||
#include "test_circ.moc"
|
||||
|
||||
constexpr int LoopCount = 1000000;
|
||||
//constexpr int LoopCount = 1000/*0000*/;
|
||||
|
||||
void Unit::initTestCase() {
|
||||
TestSuite::initTestCase();
|
||||
cq__ = new cq_t;
|
||||
}
|
||||
|
||||
void Unit::cleanupTestCase() {
|
||||
delete cq__;
|
||||
}
|
||||
|
||||
void Unit::test_inst() {
|
||||
std::cout << "cq_t::head_size = " << cq_t::head_size << std::endl;
|
||||
std::cout << "cq_t::data_size = " << cq_t::data_size << std::endl;
|
||||
std::cout << "cq_t::elem_size = " << cq_t::elem_size << std::endl;
|
||||
std::cout << "cq_t::block_size = " << cq_t::block_size << std::endl;
|
||||
|
||||
QCOMPARE(static_cast<std::size_t>(cq_t::data_size), sizeof(msg_t));
|
||||
|
||||
std::cout << "sizeof(ea_t<sizeof(msg_t)>) = " << sizeof(*cq__) << std::endl;
|
||||
}
|
||||
|
||||
template <int N, int M, bool V = true, int Loops = LoopCount>
|
||||
void test_prod_cons() {
|
||||
benchmark_prod_cons<N, M, Loops, std::conditional_t<V, cq_t, void>>(cq__);
|
||||
}
|
||||
|
||||
void Unit::test_prod_cons_1v1() {
|
||||
// ea_t<
|
||||
// sizeof(msg_t),
|
||||
// pc_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast>
|
||||
// > el_arr_mmb;
|
||||
// benchmark_prod_cons<1, 1, LoopCount, void>(&el_arr_mmb);
|
||||
// benchmark_prod_cons<2, 1, LoopCount, void>(&el_arr_mmb);
|
||||
|
||||
ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast>
|
||||
> el_arr_ssu;
|
||||
benchmark_prod_cons<1, 1, LoopCount, cq_t>(&el_arr_ssu);
|
||||
benchmark_prod_cons<1, 1, LoopCount, void>(&el_arr_ssu);
|
||||
|
||||
ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::single, ipc::relat::multi, ipc::trans::unicast>
|
||||
> el_arr_smu;
|
||||
benchmark_prod_cons<1, 1, LoopCount, decltype(el_arr_smu)::policy_t>(&el_arr_smu);
|
||||
benchmark_prod_cons<1, 1, LoopCount, void>(&el_arr_smu);
|
||||
|
||||
ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::unicast>
|
||||
> el_arr_mmu;
|
||||
benchmark_prod_cons<1, 1, LoopCount, decltype(el_arr_mmu)::policy_t>(&el_arr_mmu);
|
||||
benchmark_prod_cons<1, 1, LoopCount, void>(&el_arr_mmu);
|
||||
|
||||
test_prod_cons<1, 1>();
|
||||
test_prod_cons<1, 1, false>();
|
||||
|
||||
ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast>
|
||||
> el_arr_mmb;
|
||||
benchmark_prod_cons<1, 1, LoopCount, cq_t>(&el_arr_mmb);
|
||||
benchmark_prod_cons<1, 1, LoopCount, void>(&el_arr_mmb);
|
||||
}
|
||||
|
||||
void Unit::test_prod_cons_1v3() {
|
||||
ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::single, ipc::relat::multi, ipc::trans::unicast>
|
||||
> el_arr_smu;
|
||||
benchmark_prod_cons<1, 3, LoopCount, decltype(el_arr_smu)::policy_t>(&el_arr_smu);
|
||||
benchmark_prod_cons<1, 3, LoopCount, void>(&el_arr_smu);
|
||||
|
||||
ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::unicast>
|
||||
> el_arr_mmu;
|
||||
benchmark_prod_cons<1, 3, LoopCount, decltype(el_arr_mmu)::policy_t>(&el_arr_mmu);
|
||||
benchmark_prod_cons<1, 3, LoopCount, void>(&el_arr_mmu);
|
||||
|
||||
test_prod_cons<1, 3>();
|
||||
test_prod_cons<1, 3, false>();
|
||||
|
||||
ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast>
|
||||
> el_arr_mmb;
|
||||
benchmark_prod_cons<1, 3, LoopCount, cq_t>(&el_arr_mmb);
|
||||
benchmark_prod_cons<1, 3, LoopCount, void>(&el_arr_mmb);
|
||||
}
|
||||
|
||||
void Unit::test_prod_cons_performance() {
|
||||
ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::single, ipc::relat::multi, ipc::trans::unicast>
|
||||
> el_arr_smu;
|
||||
ipc::detail::static_for<8>([&el_arr_smu](auto index) {
|
||||
benchmark_prod_cons<1, decltype(index)::value + 1, LoopCount, void>(&el_arr_smu);
|
||||
});
|
||||
|
||||
ipc::detail::static_for<8>([](auto index) {
|
||||
test_prod_cons<1, decltype(index)::value + 1, false>();
|
||||
});
|
||||
test_prod_cons<1, 8>(); // test & verify
|
||||
|
||||
ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::unicast>
|
||||
> el_arr_mmu;
|
||||
ipc::detail::static_for<8>([&el_arr_mmu](auto index) {
|
||||
benchmark_prod_cons<1, decltype(index)::value + 1, LoopCount, void>(&el_arr_mmu);
|
||||
});
|
||||
ipc::detail::static_for<8>([&el_arr_mmu](auto index) {
|
||||
benchmark_prod_cons<decltype(index)::value + 1, 1, LoopCount, void>(&el_arr_mmu);
|
||||
});
|
||||
ipc::detail::static_for<8>([&el_arr_mmu](auto index) {
|
||||
benchmark_prod_cons<decltype(index)::value + 1, decltype(index)::value + 1, LoopCount, void>(&el_arr_mmu);
|
||||
});
|
||||
|
||||
ea_t<
|
||||
sizeof(msg_t),
|
||||
pc_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast>
|
||||
> el_arr_mmb;
|
||||
ipc::detail::static_for<8>([&el_arr_mmb](auto index) {
|
||||
benchmark_prod_cons<1, decltype(index)::value + 1, LoopCount, void>(&el_arr_mmb);
|
||||
});
|
||||
ipc::detail::static_for<8>([&el_arr_mmb](auto index) {
|
||||
benchmark_prod_cons<decltype(index)::value + 1, 1, LoopCount, void>(&el_arr_mmb);
|
||||
});
|
||||
ipc::detail::static_for<8>([&el_arr_mmb](auto index) {
|
||||
benchmark_prod_cons<decltype(index)::value + 1, decltype(index)::value + 1, LoopCount, void>(&el_arr_mmb);
|
||||
});
|
||||
}
|
||||
|
||||
void Unit::test_queue() {
|
||||
using queue_t = ipc::queue<msg_t, ipc::policy::choose<
|
||||
ipc::circ::elem_array,
|
||||
ipc::wr<ipc::relat::single, ipc::relat::multi, ipc::trans::broadcast>
|
||||
>>;
|
||||
queue_t queue;
|
||||
|
||||
QVERIFY(!queue.push(msg_t { 1, 2 }));
|
||||
msg_t msg {};
|
||||
QVERIFY(!queue.pop(msg));
|
||||
QCOMPARE(msg, (msg_t {}));
|
||||
QVERIFY(sizeof(decltype(queue)::elems_t) <= sizeof(*cq__));
|
||||
|
||||
ipc::detail::static_for<16>([](auto index) {
|
||||
benchmark_prod_cons<1, decltype(index)::value + 1, LoopCount>((queue_t*)nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
964
test/test_ipc.cpp
Executable file → Normal file
964
test/test_ipc.cpp
Executable file → Normal file
@ -1,482 +1,482 @@
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <type_traits>
|
||||
#include <iostream>
|
||||
#include <shared_mutex>
|
||||
#include <mutex>
|
||||
#include <typeinfo>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
#include "stopwatch.hpp"
|
||||
#include "spin_lock.hpp"
|
||||
#include "random.hpp"
|
||||
|
||||
#include "ipc.h"
|
||||
#include "rw_lock.h"
|
||||
#include "memory/resource.h"
|
||||
|
||||
#include "test.h"
|
||||
|
||||
namespace {
|
||||
|
||||
std::vector<ipc::buff_t> datas__;
|
||||
|
||||
constexpr int DataMin = 2;
|
||||
constexpr int DataMax = 256;
|
||||
constexpr int LoopCount = 100000;
|
||||
//constexpr int LoopCount = 1000;
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
template <typename T>
|
||||
struct test_verify {
|
||||
std::vector<std::vector<ipc::buff_t>> list_;
|
||||
|
||||
test_verify(int M)
|
||||
: list_(static_cast<std::size_t>(M))
|
||||
{}
|
||||
|
||||
void prepare(void* /*pt*/) {}
|
||||
|
||||
void push_data(int cid, ipc::buff_t & msg) {
|
||||
list_[cid].emplace_back(std::move(msg));
|
||||
}
|
||||
|
||||
void verify(int /*N*/, int /*Loops*/) {
|
||||
std::cout << "verifying..." << std::endl;
|
||||
for (auto& c_dats : list_) {
|
||||
QCOMPARE(datas__.size(), c_dats.size());
|
||||
std::size_t i = 0;
|
||||
for (auto& d : c_dats) {
|
||||
QCOMPARE(datas__[i++], d);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct test_cq<ipc::route> {
|
||||
using cn_t = ipc::route;
|
||||
|
||||
std::string conn_name_;
|
||||
|
||||
test_cq(void*)
|
||||
: conn_name_("test-ipc-route") {
|
||||
}
|
||||
|
||||
cn_t connect() {
|
||||
return cn_t { conn_name_.c_str() };
|
||||
}
|
||||
|
||||
void disconnect(cn_t& cn) {
|
||||
cn.disconnect();
|
||||
}
|
||||
|
||||
void wait_start(int M) {
|
||||
cn_t::wait_for_recv(conn_name_.c_str(), static_cast<std::size_t>(M));
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void recv(cn_t& cn, F&& proc) {
|
||||
do {
|
||||
auto msg = cn.recv();
|
||||
if (msg.size() < 2) {
|
||||
QCOMPARE(msg, ipc::buff_t('\0'));
|
||||
return;
|
||||
}
|
||||
proc(msg);
|
||||
} while(1);
|
||||
}
|
||||
|
||||
cn_t connect_send() {
|
||||
return connect();
|
||||
}
|
||||
|
||||
void send(cn_t& cn, const std::array<int, 2>& info) {
|
||||
int n = info[1];
|
||||
if (n < 0) {
|
||||
/*QVERIFY*/(cn.send(ipc::buff_t('\0')));
|
||||
}
|
||||
else /*QVERIFY*/(cn.send(datas__[static_cast<decltype(datas__)::size_type>(n)]));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct test_cq<ipc::channel> {
|
||||
using cn_t = ipc::channel;
|
||||
|
||||
std::string conn_name_;
|
||||
int m_ = 0;
|
||||
|
||||
test_cq(void*)
|
||||
: conn_name_("test-ipc-channel") {
|
||||
}
|
||||
|
||||
cn_t connect() {
|
||||
return cn_t { conn_name_.c_str() };
|
||||
}
|
||||
|
||||
void disconnect(cn_t& cn) {
|
||||
cn.disconnect();
|
||||
}
|
||||
|
||||
void wait_start(int M) { m_ = M; }
|
||||
|
||||
template <typename F>
|
||||
void recv(cn_t& cn, F&& proc) {
|
||||
do {
|
||||
auto msg = cn.recv();
|
||||
if (msg.size() < 2) {
|
||||
QCOMPARE(msg, ipc::buff_t('\0'));
|
||||
return;
|
||||
}
|
||||
proc(msg);
|
||||
} while(1);
|
||||
}
|
||||
|
||||
cn_t connect_send() {
|
||||
return connect();
|
||||
}
|
||||
|
||||
void send(cn_t& cn, const std::array<int, 2>& info) {
|
||||
thread_local struct s_dummy {
|
||||
s_dummy(cn_t& cn, int m) {
|
||||
cn.wait_for_recv(static_cast<std::size_t>(m));
|
||||
// std::printf("start to send: %d.\n", m);
|
||||
}
|
||||
} _(cn, m_);
|
||||
int n = info[1];
|
||||
if (n < 0) {
|
||||
/*QVERIFY*/(cn.send(ipc::buff_t('\0')));
|
||||
}
|
||||
else /*QVERIFY*/(cn.send(datas__[static_cast<decltype(datas__)::size_type>(n)]));
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
class Unit : public TestSuite {
|
||||
Q_OBJECT
|
||||
|
||||
const char* name() const {
|
||||
return "test_ipc";
|
||||
}
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
|
||||
void test_rw_lock();
|
||||
void test_route();
|
||||
void test_route_rtt();
|
||||
void test_route_performance();
|
||||
void test_channel();
|
||||
void test_channel_rtt();
|
||||
void test_channel_performance();
|
||||
} unit__;
|
||||
|
||||
#include "test_ipc.moc"
|
||||
|
||||
void Unit::initTestCase() {
|
||||
TestSuite::initTestCase();
|
||||
|
||||
capo::random<> rdm { DataMin, DataMax };
|
||||
capo::random<> bit { 0, (std::numeric_limits<ipc::byte_t>::max)() };
|
||||
|
||||
for (int i = 0; i < LoopCount; ++i) {
|
||||
std::size_t n = static_cast<std::size_t>(rdm());
|
||||
ipc::buff_t buff {
|
||||
new ipc::byte_t[n], n,
|
||||
[](void* p, std::size_t) {
|
||||
delete [] static_cast<ipc::byte_t*>(p);
|
||||
}
|
||||
};
|
||||
for (std::size_t k = 0; k < buff.size(); ++k) {
|
||||
static_cast<ipc::byte_t*>(buff.data())[k] = static_cast<ipc::byte_t>(bit());
|
||||
}
|
||||
datas__.emplace_back(std::move(buff));
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::cleanupTestCase() {
|
||||
datas__.clear();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr T acc(T b, T e) {
|
||||
return (e + b) * (e - b + 1) / 2;
|
||||
}
|
||||
|
||||
template <typename Mutex>
|
||||
struct lc_wrapper : Mutex {
|
||||
void lock_shared () { Mutex::lock (); }
|
||||
void unlock_shared() { Mutex::unlock(); }
|
||||
};
|
||||
|
||||
template <typename Lc, int W, int R, int Loops = LoopCount>
|
||||
void benchmark_lc() {
|
||||
std::thread w_trd[W];
|
||||
std::thread r_trd[R];
|
||||
std::atomic_int fini { 0 };
|
||||
// std::atomic_bool wf { false };
|
||||
|
||||
std::vector<int> datas;
|
||||
Lc lc;
|
||||
|
||||
test_stopwatch sw;
|
||||
std::cout << std::endl << type_name<Lc>() << std::endl;
|
||||
|
||||
for (auto& t : r_trd) {
|
||||
t = std::thread([&] {
|
||||
std::vector<int> seq;
|
||||
std::size_t cnt = 0;
|
||||
while (1) {
|
||||
int x = -1;
|
||||
{
|
||||
std::shared_lock<Lc> guard { lc };
|
||||
// QVERIFY(!wf);
|
||||
if (cnt < datas.size()) {
|
||||
x = datas[cnt];
|
||||
}
|
||||
// std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
if (x == 0) break; // quit
|
||||
if (x != -1) {
|
||||
seq.push_back(x);
|
||||
++cnt;
|
||||
}
|
||||
}
|
||||
std::this_thread::yield();
|
||||
}
|
||||
if ((fini.fetch_add(1, std::memory_order_relaxed) + 1) == R) {
|
||||
sw.print_elapsed(W, R, Loops);
|
||||
}
|
||||
std::uint64_t sum = 0;
|
||||
for (int i : seq) sum += static_cast<std::uint64_t>(i);
|
||||
QCOMPARE(sum, acc<std::uint64_t>(1, Loops) * W);
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& t : w_trd) {
|
||||
t = std::thread([&] {
|
||||
sw.start();
|
||||
for (int i = 1; i <= Loops; ++i) {
|
||||
{
|
||||
std::unique_lock<Lc> guard { lc };
|
||||
// wf = true;
|
||||
datas.push_back(i);
|
||||
// std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
// wf = false;
|
||||
}
|
||||
std::this_thread::yield();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& t : w_trd) t.join();
|
||||
lc.lock();
|
||||
datas.push_back(0);
|
||||
lc.unlock();
|
||||
for (auto& t : r_trd) t.join();
|
||||
}
|
||||
|
||||
template <int W, int R>
|
||||
void test_lock_performance() {
|
||||
|
||||
std::cout << std::endl
|
||||
<< "test_lock_performance: [" << W << ":" << R << "]"
|
||||
<< std::endl;
|
||||
|
||||
benchmark_lc<ipc::rw_lock , W, R>();
|
||||
benchmark_lc<lc_wrapper< ipc::spin_lock>, W, R>();
|
||||
benchmark_lc<lc_wrapper<capo::spin_lock>, W, R>();
|
||||
benchmark_lc<lc_wrapper<std::mutex> , W, R>();
|
||||
benchmark_lc<std::shared_timed_mutex , W, R>();
|
||||
}
|
||||
|
||||
void Unit::test_rw_lock() {
|
||||
// test_lock_performance<1, 1>();
|
||||
// test_lock_performance<4, 4>();
|
||||
// test_lock_performance<1, 8>();
|
||||
// test_lock_performance<8, 1>();
|
||||
}
|
||||
|
||||
template <typename T, int N, int M, bool V = true, int Loops = LoopCount>
|
||||
void test_prod_cons() {
|
||||
benchmark_prod_cons<N, M, Loops, std::conditional_t<V, T, void>>((T*)nullptr);
|
||||
}
|
||||
|
||||
void Unit::test_route() {
|
||||
//return;
|
||||
std::vector<char const *> const datas = {
|
||||
"hello!",
|
||||
"foo",
|
||||
"bar",
|
||||
"ISO/IEC",
|
||||
"14882:2011",
|
||||
"ISO/IEC 14882:2017 Information technology - Programming languages - C++",
|
||||
"ISO/IEC 14882:2020",
|
||||
"Modern C++ Design: Generic Programming and Design Patterns Applied"
|
||||
};
|
||||
|
||||
std::thread t1 {[&] {
|
||||
ipc::route cc { "my-ipc-route" };
|
||||
for (std::size_t i = 0; i < datas.size(); ++i) {
|
||||
ipc::buff_t dd = cc.recv();
|
||||
std::cout << "recv: " << (char*)dd.data() << std::endl;
|
||||
QCOMPARE(dd.size(), std::strlen(datas[i]) + 1);
|
||||
QVERIFY(std::memcmp(dd.data(), datas[i], dd.size()) == 0);
|
||||
}
|
||||
}};
|
||||
|
||||
std::thread t2 {[&] {
|
||||
ipc::route cc { "my-ipc-route" };
|
||||
while (cc.recv_count() == 0) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
for (std::size_t i = 0; i < datas.size(); ++i) {
|
||||
std::cout << "sending: " << datas[i] << std::endl;
|
||||
QVERIFY(cc.send(datas[i]));
|
||||
}
|
||||
}};
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
test_prod_cons<ipc::route, 1, 1>(); // test & verify
|
||||
}
|
||||
|
||||
void Unit::test_route_rtt() {
|
||||
//return;
|
||||
test_stopwatch sw;
|
||||
|
||||
std::thread t1 {[&] {
|
||||
ipc::route cc { "my-ipc-route-1" };
|
||||
ipc::route cr { "my-ipc-route-2" };
|
||||
for (std::size_t i = 0;; ++i) {
|
||||
auto dd = cc.recv();
|
||||
if (dd.size() < 2) return;
|
||||
//std::cout << "recv: " << i << "-[" << dd.size() << "]" << std::endl;
|
||||
while (!cr.send(ipc::buff_t('a'))) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
}};
|
||||
|
||||
std::thread t2 {[&] {
|
||||
ipc::route cc { "my-ipc-route-1" };
|
||||
ipc::route cr { "my-ipc-route-2" };
|
||||
while (cc.recv_count() == 0) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
sw.start();
|
||||
for (std::size_t i = 0; i < LoopCount; ++i) {
|
||||
cc.send(datas__[i]);
|
||||
//std::cout << "sent: " << i << "-[" << datas__[i].size() << "]" << std::endl;
|
||||
/*auto dd = */cr.recv();
|
||||
// if (dd.size() != 1 || dd[0] != 'a') {
|
||||
// QVERIFY(false);
|
||||
// }
|
||||
}
|
||||
cc.send(ipc::buff_t('\0'));
|
||||
t1.join();
|
||||
sw.print_elapsed(1, 1, LoopCount);
|
||||
}};
|
||||
|
||||
t2.join();
|
||||
}
|
||||
|
||||
void Unit::test_route_performance() {
|
||||
//return;
|
||||
ipc::detail::static_for<8>([](auto index) {
|
||||
test_prod_cons<ipc::route, 1, decltype(index)::value + 1, false>();
|
||||
});
|
||||
test_prod_cons<ipc::route, 1, 8>(); // test & verify
|
||||
}
|
||||
|
||||
void Unit::test_channel() {
|
||||
//return;
|
||||
std::thread t1 {[&] {
|
||||
ipc::channel cc { "my-ipc-channel" };
|
||||
for (std::size_t i = 0;; ++i) {
|
||||
ipc::buff_t dd = cc.recv();
|
||||
if (dd.size() < 2) return;
|
||||
QCOMPARE(dd, datas__[i]);
|
||||
}
|
||||
}};
|
||||
|
||||
std::thread t2 {[&] {
|
||||
ipc::channel cc { "my-ipc-channel" };
|
||||
cc.wait_for_recv(1);
|
||||
for (std::size_t i = 0; i < static_cast<std::size_t>((std::min)(100, LoopCount)); ++i) {
|
||||
std::cout << "sending: " << i << "-[" << datas__[i].size() << "]" << std::endl;
|
||||
cc.send(datas__[i]);
|
||||
}
|
||||
cc.send(ipc::buff_t('\0'));
|
||||
t1.join();
|
||||
}};
|
||||
|
||||
t2.join();
|
||||
}
|
||||
|
||||
void Unit::test_channel_rtt() {
|
||||
test_stopwatch sw;
|
||||
|
||||
std::thread t1 {[&] {
|
||||
ipc::channel cc { "my-ipc-channel" };
|
||||
bool recv_2 = false;
|
||||
for (std::size_t i = 0;; ++i) {
|
||||
auto dd = cc.recv();
|
||||
if (dd.size() < 2) return;
|
||||
//if (i % 1000 == 0) {
|
||||
// std::cout << "recv: " << i << "-[" << dd.size() << "]" << std::endl;
|
||||
//}
|
||||
while (!recv_2) {
|
||||
recv_2 = cc.wait_for_recv(2);
|
||||
}
|
||||
cc.send(ipc::buff_t('a'));
|
||||
}
|
||||
}};
|
||||
|
||||
std::thread t2 {[&] {
|
||||
ipc::channel cc { "my-ipc-channel" };
|
||||
cc.wait_for_recv(1);
|
||||
sw.start();
|
||||
for (std::size_t i = 0; i < LoopCount; ++i) {
|
||||
//if (i % 1000 == 0) {
|
||||
// std::cout << "send: " << i << "-[" << datas__[i].size() << "]" << std::endl;
|
||||
//}
|
||||
cc.send(datas__[i]);
|
||||
/*auto dd = */cc.recv();
|
||||
//if (dd.size() != 1 || dd.data<char>()[0] != 'a') {
|
||||
// std::cout << "recv ack fail: " << i << "-[" << dd.size() << "]" << std::endl;
|
||||
// QVERIFY(false);
|
||||
//}
|
||||
}
|
||||
cc.send(ipc::buff_t('\0'));
|
||||
t1.join();
|
||||
sw.print_elapsed(1, 1, LoopCount);
|
||||
}};
|
||||
|
||||
t2.join();
|
||||
}
|
||||
|
||||
void Unit::test_channel_performance() {
|
||||
ipc::detail::static_for<8>([](auto index) {
|
||||
test_prod_cons<ipc::channel, 1, decltype(index)::value + 1, false>();
|
||||
});
|
||||
ipc::detail::static_for<8>([](auto index) {
|
||||
test_prod_cons<ipc::channel, decltype(index)::value + 1, 1, false>();
|
||||
});
|
||||
ipc::detail::static_for<8>([](auto index) {
|
||||
test_prod_cons<ipc::channel, decltype(index)::value + 1,
|
||||
decltype(index)::value + 1, false>();
|
||||
});
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <type_traits>
|
||||
#include <iostream>
|
||||
#include <shared_mutex>
|
||||
#include <mutex>
|
||||
#include <typeinfo>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
#include "stopwatch.hpp"
|
||||
#include "spin_lock.hpp"
|
||||
#include "random.hpp"
|
||||
|
||||
#include "ipc.h"
|
||||
#include "rw_lock.h"
|
||||
#include "memory/resource.h"
|
||||
|
||||
#include "test.h"
|
||||
|
||||
namespace {
|
||||
|
||||
std::vector<ipc::buff_t> datas__;
|
||||
|
||||
constexpr int DataMin = 2;
|
||||
constexpr int DataMax = 256;
|
||||
constexpr int LoopCount = 100000;
|
||||
//constexpr int LoopCount = 1000;
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
template <typename T>
|
||||
struct test_verify {
|
||||
std::vector<std::vector<ipc::buff_t>> list_;
|
||||
|
||||
test_verify(int M)
|
||||
: list_(static_cast<std::size_t>(M))
|
||||
{}
|
||||
|
||||
void prepare(void* /*pt*/) {}
|
||||
|
||||
void push_data(int cid, ipc::buff_t & msg) {
|
||||
list_[cid].emplace_back(std::move(msg));
|
||||
}
|
||||
|
||||
void verify(int /*N*/, int /*Loops*/) {
|
||||
std::cout << "verifying..." << std::endl;
|
||||
for (auto& c_dats : list_) {
|
||||
QCOMPARE(datas__.size(), c_dats.size());
|
||||
std::size_t i = 0;
|
||||
for (auto& d : c_dats) {
|
||||
QCOMPARE(datas__[i++], d);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct test_cq<ipc::route> {
|
||||
using cn_t = ipc::route;
|
||||
|
||||
std::string conn_name_;
|
||||
|
||||
test_cq(void*)
|
||||
: conn_name_("test-ipc-route") {
|
||||
}
|
||||
|
||||
cn_t connect() {
|
||||
return cn_t { conn_name_.c_str() };
|
||||
}
|
||||
|
||||
void disconnect(cn_t& cn) {
|
||||
cn.disconnect();
|
||||
}
|
||||
|
||||
void wait_start(int M) {
|
||||
cn_t::wait_for_recv(conn_name_.c_str(), static_cast<std::size_t>(M));
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void recv(cn_t& cn, F&& proc) {
|
||||
do {
|
||||
auto msg = cn.recv();
|
||||
if (msg.size() < 2) {
|
||||
QCOMPARE(msg, ipc::buff_t('\0'));
|
||||
return;
|
||||
}
|
||||
proc(msg);
|
||||
} while(1);
|
||||
}
|
||||
|
||||
cn_t connect_send() {
|
||||
return connect();
|
||||
}
|
||||
|
||||
void send(cn_t& cn, const std::array<int, 2>& info) {
|
||||
int n = info[1];
|
||||
if (n < 0) {
|
||||
/*QVERIFY*/(cn.send(ipc::buff_t('\0')));
|
||||
}
|
||||
else /*QVERIFY*/(cn.send(datas__[static_cast<decltype(datas__)::size_type>(n)]));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct test_cq<ipc::channel> {
|
||||
using cn_t = ipc::channel;
|
||||
|
||||
std::string conn_name_;
|
||||
int m_ = 0;
|
||||
|
||||
test_cq(void*)
|
||||
: conn_name_("test-ipc-channel") {
|
||||
}
|
||||
|
||||
cn_t connect() {
|
||||
return cn_t { conn_name_.c_str() };
|
||||
}
|
||||
|
||||
void disconnect(cn_t& cn) {
|
||||
cn.disconnect();
|
||||
}
|
||||
|
||||
void wait_start(int M) { m_ = M; }
|
||||
|
||||
template <typename F>
|
||||
void recv(cn_t& cn, F&& proc) {
|
||||
do {
|
||||
auto msg = cn.recv();
|
||||
if (msg.size() < 2) {
|
||||
QCOMPARE(msg, ipc::buff_t('\0'));
|
||||
return;
|
||||
}
|
||||
proc(msg);
|
||||
} while(1);
|
||||
}
|
||||
|
||||
cn_t connect_send() {
|
||||
return connect();
|
||||
}
|
||||
|
||||
void send(cn_t& cn, const std::array<int, 2>& info) {
|
||||
thread_local struct s_dummy {
|
||||
s_dummy(cn_t& cn, int m) {
|
||||
cn.wait_for_recv(static_cast<std::size_t>(m));
|
||||
// std::printf("start to send: %d.\n", m);
|
||||
}
|
||||
} _(cn, m_);
|
||||
int n = info[1];
|
||||
if (n < 0) {
|
||||
/*QVERIFY*/(cn.send(ipc::buff_t('\0')));
|
||||
}
|
||||
else /*QVERIFY*/(cn.send(datas__[static_cast<decltype(datas__)::size_type>(n)]));
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
class Unit : public TestSuite {
|
||||
Q_OBJECT
|
||||
|
||||
const char* name() const {
|
||||
return "test_ipc";
|
||||
}
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
|
||||
void test_rw_lock();
|
||||
void test_route();
|
||||
void test_route_rtt();
|
||||
void test_route_performance();
|
||||
void test_channel();
|
||||
void test_channel_rtt();
|
||||
void test_channel_performance();
|
||||
} unit__;
|
||||
|
||||
#include "test_ipc.moc"
|
||||
|
||||
void Unit::initTestCase() {
|
||||
TestSuite::initTestCase();
|
||||
|
||||
capo::random<> rdm { DataMin, DataMax };
|
||||
capo::random<> bit { 0, (std::numeric_limits<ipc::byte_t>::max)() };
|
||||
|
||||
for (int i = 0; i < LoopCount; ++i) {
|
||||
std::size_t n = static_cast<std::size_t>(rdm());
|
||||
ipc::buff_t buff {
|
||||
new ipc::byte_t[n], n,
|
||||
[](void* p, std::size_t) {
|
||||
delete [] static_cast<ipc::byte_t*>(p);
|
||||
}
|
||||
};
|
||||
for (std::size_t k = 0; k < buff.size(); ++k) {
|
||||
static_cast<ipc::byte_t*>(buff.data())[k] = static_cast<ipc::byte_t>(bit());
|
||||
}
|
||||
datas__.emplace_back(std::move(buff));
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::cleanupTestCase() {
|
||||
datas__.clear();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr T acc(T b, T e) {
|
||||
return (e + b) * (e - b + 1) / 2;
|
||||
}
|
||||
|
||||
template <typename Mutex>
|
||||
struct lc_wrapper : Mutex {
|
||||
void lock_shared () { Mutex::lock (); }
|
||||
void unlock_shared() { Mutex::unlock(); }
|
||||
};
|
||||
|
||||
template <typename Lc, int W, int R, int Loops = LoopCount>
|
||||
void benchmark_lc() {
|
||||
std::thread w_trd[W];
|
||||
std::thread r_trd[R];
|
||||
std::atomic_int fini { 0 };
|
||||
// std::atomic_bool wf { false };
|
||||
|
||||
std::vector<int> datas;
|
||||
Lc lc;
|
||||
|
||||
test_stopwatch sw;
|
||||
std::cout << std::endl << type_name<Lc>() << std::endl;
|
||||
|
||||
for (auto& t : r_trd) {
|
||||
t = std::thread([&] {
|
||||
std::vector<int> seq;
|
||||
std::size_t cnt = 0;
|
||||
while (1) {
|
||||
int x = -1;
|
||||
{
|
||||
std::shared_lock<Lc> guard { lc };
|
||||
// QVERIFY(!wf);
|
||||
if (cnt < datas.size()) {
|
||||
x = datas[cnt];
|
||||
}
|
||||
// std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
if (x == 0) break; // quit
|
||||
if (x != -1) {
|
||||
seq.push_back(x);
|
||||
++cnt;
|
||||
}
|
||||
}
|
||||
std::this_thread::yield();
|
||||
}
|
||||
if ((fini.fetch_add(1, std::memory_order_relaxed) + 1) == R) {
|
||||
sw.print_elapsed(W, R, Loops);
|
||||
}
|
||||
std::uint64_t sum = 0;
|
||||
for (int i : seq) sum += static_cast<std::uint64_t>(i);
|
||||
QCOMPARE(sum, acc<std::uint64_t>(1, Loops) * W);
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& t : w_trd) {
|
||||
t = std::thread([&] {
|
||||
sw.start();
|
||||
for (int i = 1; i <= Loops; ++i) {
|
||||
{
|
||||
std::unique_lock<Lc> guard { lc };
|
||||
// wf = true;
|
||||
datas.push_back(i);
|
||||
// std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
// wf = false;
|
||||
}
|
||||
std::this_thread::yield();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& t : w_trd) t.join();
|
||||
lc.lock();
|
||||
datas.push_back(0);
|
||||
lc.unlock();
|
||||
for (auto& t : r_trd) t.join();
|
||||
}
|
||||
|
||||
template <int W, int R>
|
||||
void test_lock_performance() {
|
||||
|
||||
std::cout << std::endl
|
||||
<< "test_lock_performance: [" << W << ":" << R << "]"
|
||||
<< std::endl;
|
||||
|
||||
benchmark_lc<ipc::rw_lock , W, R>();
|
||||
benchmark_lc<lc_wrapper< ipc::spin_lock>, W, R>();
|
||||
benchmark_lc<lc_wrapper<capo::spin_lock>, W, R>();
|
||||
benchmark_lc<lc_wrapper<std::mutex> , W, R>();
|
||||
benchmark_lc<std::shared_timed_mutex , W, R>();
|
||||
}
|
||||
|
||||
void Unit::test_rw_lock() {
|
||||
// test_lock_performance<1, 1>();
|
||||
// test_lock_performance<4, 4>();
|
||||
// test_lock_performance<1, 8>();
|
||||
// test_lock_performance<8, 1>();
|
||||
}
|
||||
|
||||
template <typename T, int N, int M, bool V = true, int Loops = LoopCount>
|
||||
void test_prod_cons() {
|
||||
benchmark_prod_cons<N, M, Loops, std::conditional_t<V, T, void>>((T*)nullptr);
|
||||
}
|
||||
|
||||
void Unit::test_route() {
|
||||
//return;
|
||||
std::vector<char const *> const datas = {
|
||||
"hello!",
|
||||
"foo",
|
||||
"bar",
|
||||
"ISO/IEC",
|
||||
"14882:2011",
|
||||
"ISO/IEC 14882:2017 Information technology - Programming languages - C++",
|
||||
"ISO/IEC 14882:2020",
|
||||
"Modern C++ Design: Generic Programming and Design Patterns Applied"
|
||||
};
|
||||
|
||||
std::thread t1 {[&] {
|
||||
ipc::route cc { "my-ipc-route" };
|
||||
for (std::size_t i = 0; i < datas.size(); ++i) {
|
||||
ipc::buff_t dd = cc.recv();
|
||||
std::cout << "recv: " << (char*)dd.data() << std::endl;
|
||||
QCOMPARE(dd.size(), std::strlen(datas[i]) + 1);
|
||||
QVERIFY(std::memcmp(dd.data(), datas[i], dd.size()) == 0);
|
||||
}
|
||||
}};
|
||||
|
||||
std::thread t2 {[&] {
|
||||
ipc::route cc { "my-ipc-route" };
|
||||
while (cc.recv_count() == 0) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
for (std::size_t i = 0; i < datas.size(); ++i) {
|
||||
std::cout << "sending: " << datas[i] << std::endl;
|
||||
QVERIFY(cc.send(datas[i]));
|
||||
}
|
||||
}};
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
test_prod_cons<ipc::route, 1, 1>(); // test & verify
|
||||
}
|
||||
|
||||
void Unit::test_route_rtt() {
|
||||
//return;
|
||||
test_stopwatch sw;
|
||||
|
||||
std::thread t1 {[&] {
|
||||
ipc::route cc { "my-ipc-route-1" };
|
||||
ipc::route cr { "my-ipc-route-2" };
|
||||
for (std::size_t i = 0;; ++i) {
|
||||
auto dd = cc.recv();
|
||||
if (dd.size() < 2) return;
|
||||
//std::cout << "recv: " << i << "-[" << dd.size() << "]" << std::endl;
|
||||
while (!cr.send(ipc::buff_t('a'))) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
}};
|
||||
|
||||
std::thread t2 {[&] {
|
||||
ipc::route cc { "my-ipc-route-1" };
|
||||
ipc::route cr { "my-ipc-route-2" };
|
||||
while (cc.recv_count() == 0) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
sw.start();
|
||||
for (std::size_t i = 0; i < LoopCount; ++i) {
|
||||
cc.send(datas__[i]);
|
||||
//std::cout << "sent: " << i << "-[" << datas__[i].size() << "]" << std::endl;
|
||||
/*auto dd = */cr.recv();
|
||||
// if (dd.size() != 1 || dd[0] != 'a') {
|
||||
// QVERIFY(false);
|
||||
// }
|
||||
}
|
||||
cc.send(ipc::buff_t('\0'));
|
||||
t1.join();
|
||||
sw.print_elapsed(1, 1, LoopCount);
|
||||
}};
|
||||
|
||||
t2.join();
|
||||
}
|
||||
|
||||
void Unit::test_route_performance() {
|
||||
//return;
|
||||
ipc::detail::static_for<8>([](auto index) {
|
||||
test_prod_cons<ipc::route, 1, decltype(index)::value + 1, false>();
|
||||
});
|
||||
test_prod_cons<ipc::route, 1, 8>(); // test & verify
|
||||
}
|
||||
|
||||
void Unit::test_channel() {
|
||||
//return;
|
||||
std::thread t1 {[&] {
|
||||
ipc::channel cc { "my-ipc-channel" };
|
||||
for (std::size_t i = 0;; ++i) {
|
||||
ipc::buff_t dd = cc.recv();
|
||||
if (dd.size() < 2) return;
|
||||
QCOMPARE(dd, datas__[i]);
|
||||
}
|
||||
}};
|
||||
|
||||
std::thread t2 {[&] {
|
||||
ipc::channel cc { "my-ipc-channel" };
|
||||
cc.wait_for_recv(1);
|
||||
for (std::size_t i = 0; i < static_cast<std::size_t>((std::min)(100, LoopCount)); ++i) {
|
||||
std::cout << "sending: " << i << "-[" << datas__[i].size() << "]" << std::endl;
|
||||
cc.send(datas__[i]);
|
||||
}
|
||||
cc.send(ipc::buff_t('\0'));
|
||||
t1.join();
|
||||
}};
|
||||
|
||||
t2.join();
|
||||
}
|
||||
|
||||
void Unit::test_channel_rtt() {
|
||||
test_stopwatch sw;
|
||||
|
||||
std::thread t1 {[&] {
|
||||
ipc::channel cc { "my-ipc-channel" };
|
||||
bool recv_2 = false;
|
||||
for (std::size_t i = 0;; ++i) {
|
||||
auto dd = cc.recv();
|
||||
if (dd.size() < 2) return;
|
||||
//if (i % 1000 == 0) {
|
||||
// std::cout << "recv: " << i << "-[" << dd.size() << "]" << std::endl;
|
||||
//}
|
||||
while (!recv_2) {
|
||||
recv_2 = cc.wait_for_recv(2);
|
||||
}
|
||||
cc.send(ipc::buff_t('a'));
|
||||
}
|
||||
}};
|
||||
|
||||
std::thread t2 {[&] {
|
||||
ipc::channel cc { "my-ipc-channel" };
|
||||
cc.wait_for_recv(1);
|
||||
sw.start();
|
||||
for (std::size_t i = 0; i < LoopCount; ++i) {
|
||||
//if (i % 1000 == 0) {
|
||||
// std::cout << "send: " << i << "-[" << datas__[i].size() << "]" << std::endl;
|
||||
//}
|
||||
cc.send(datas__[i]);
|
||||
/*auto dd = */cc.recv();
|
||||
//if (dd.size() != 1 || dd.data<char>()[0] != 'a') {
|
||||
// std::cout << "recv ack fail: " << i << "-[" << dd.size() << "]" << std::endl;
|
||||
// QVERIFY(false);
|
||||
//}
|
||||
}
|
||||
cc.send(ipc::buff_t('\0'));
|
||||
t1.join();
|
||||
sw.print_elapsed(1, 1, LoopCount);
|
||||
}};
|
||||
|
||||
t2.join();
|
||||
}
|
||||
|
||||
void Unit::test_channel_performance() {
|
||||
ipc::detail::static_for<8>([](auto index) {
|
||||
test_prod_cons<ipc::channel, 1, decltype(index)::value + 1, false>();
|
||||
});
|
||||
ipc::detail::static_for<8>([](auto index) {
|
||||
test_prod_cons<ipc::channel, decltype(index)::value + 1, 1, false>();
|
||||
});
|
||||
ipc::detail::static_for<8>([](auto index) {
|
||||
test_prod_cons<ipc::channel, decltype(index)::value + 1,
|
||||
decltype(index)::value + 1, false>();
|
||||
});
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
375
test/test_mem.cpp
Executable file → Normal file
375
test/test_mem.cpp
Executable file → Normal file
@ -1,186 +1,189 @@
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
|
||||
#include "random.hpp"
|
||||
|
||||
#include "memory/resource.h"
|
||||
#include "pool_alloc.h"
|
||||
|
||||
#include "test.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class Unit : public TestSuite {
|
||||
Q_OBJECT
|
||||
|
||||
const char* name() const {
|
||||
return "test_mem";
|
||||
}
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
|
||||
void test_alloc_free();
|
||||
void test_static();
|
||||
void test_pool();
|
||||
} /*unit__*/;
|
||||
|
||||
#include "test_mem.moc"
|
||||
|
||||
constexpr int DataMin = sizeof(void*);
|
||||
constexpr int DataMax = sizeof(void*) * 16;
|
||||
constexpr int LoopCount = 1000000;
|
||||
|
||||
std::vector<std::size_t> sizes__;
|
||||
|
||||
template <typename M>
|
||||
struct alloc_ix_t {
|
||||
static std::vector<int> ix_[2];
|
||||
static bool inited_;
|
||||
};
|
||||
|
||||
template <typename M>
|
||||
std::vector<int> alloc_ix_t<M>::ix_[2] = { std::vector<int>(LoopCount), std::vector<int>(LoopCount) };
|
||||
template <typename M>
|
||||
bool alloc_ix_t<M>::inited_ = false;
|
||||
|
||||
struct alloc_random : alloc_ix_t<alloc_random> {
|
||||
alloc_random() {
|
||||
if (inited_) return;
|
||||
inited_ = true;
|
||||
capo::random<> rdm_index(0, LoopCount - 1);
|
||||
for (int i = 0; i < LoopCount; ++i) {
|
||||
ix_[0][static_cast<std::size_t>(i)] =
|
||||
ix_[1][static_cast<std::size_t>(i)] = rdm_index();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct alloc_LIFO : alloc_ix_t<alloc_LIFO> {
|
||||
alloc_LIFO() {
|
||||
if (inited_) return;
|
||||
inited_ = true;
|
||||
for (int i = 0, n = LoopCount - 1; i < LoopCount; ++i, --n) {
|
||||
ix_[0][static_cast<std::size_t>(i)] =
|
||||
ix_[1][static_cast<std::size_t>(n)] = i;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct alloc_FIFO : alloc_ix_t<alloc_FIFO> {
|
||||
alloc_FIFO() {
|
||||
if (inited_) return;
|
||||
inited_ = true;
|
||||
for (int i = 0; i < LoopCount; ++i) {
|
||||
ix_[0][static_cast<std::size_t>(i)] =
|
||||
ix_[1][static_cast<std::size_t>(i)] = i;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void Unit::initTestCase() {
|
||||
TestSuite::initTestCase();
|
||||
|
||||
capo::random<> rdm { DataMin, DataMax };
|
||||
for (int i = 0; i < LoopCount; ++i) {
|
||||
sizes__.emplace_back(static_cast<std::size_t>(rdm()));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename AllocT>
|
||||
void benchmark_alloc() {
|
||||
std::cout << std::endl << type_name<AllocT>() << std::endl;
|
||||
|
||||
test_stopwatch sw;
|
||||
sw.start();
|
||||
|
||||
for (std::size_t x = 0; x < 10; ++x)
|
||||
for (std::size_t n = 0; n < LoopCount; ++n) {
|
||||
std::size_t s = sizes__[n];
|
||||
AllocT::free(AllocT::alloc(s), s);
|
||||
}
|
||||
|
||||
sw.print_elapsed<1>(DataMin, DataMax, LoopCount * 10);
|
||||
}
|
||||
|
||||
void Unit::test_alloc_free() {
|
||||
benchmark_alloc<ipc::mem::static_alloc>();
|
||||
benchmark_alloc<ipc::mem::async_pool_alloc>();
|
||||
}
|
||||
|
||||
template <typename AllocT, typename ModeT, int ThreadsN>
|
||||
void benchmark_alloc() {
|
||||
std::cout << std::endl
|
||||
<< "[Threads: " << ThreadsN << ", Mode: " << type_name<ModeT>() << "] "
|
||||
<< type_name<AllocT>() << std::endl;
|
||||
|
||||
std::vector<void*> ptrs[ThreadsN];
|
||||
for (auto& vec : ptrs) {
|
||||
vec.resize(LoopCount);
|
||||
}
|
||||
ModeT mode;
|
||||
|
||||
std::atomic_int fini { 0 };
|
||||
test_stopwatch sw;
|
||||
|
||||
std::thread works[ThreadsN];
|
||||
int pid = 0;
|
||||
|
||||
for (auto& w : works) {
|
||||
w = std::thread {[&, pid] {
|
||||
sw.start();
|
||||
for (std::size_t x = 0; x < 2; ++x) {
|
||||
for(std::size_t n = 0; n < LoopCount; ++n) {
|
||||
int m = mode.ix_[x][n];
|
||||
void*& p = ptrs[pid][static_cast<std::size_t>(m)];
|
||||
std::size_t s = sizes__[static_cast<std::size_t>(m)];
|
||||
if (p == nullptr) {
|
||||
p = AllocT::alloc(s);
|
||||
}
|
||||
else {
|
||||
AllocT::free(p, s);
|
||||
p = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((fini.fetch_add(1, std::memory_order_relaxed) + 1) == ThreadsN) {
|
||||
sw.print_elapsed<1>(DataMin, DataMax, LoopCount * ThreadsN);
|
||||
}
|
||||
}};
|
||||
++pid;
|
||||
}
|
||||
sw.start();
|
||||
|
||||
for (auto& w : works) w.join();
|
||||
}
|
||||
|
||||
template <typename AllocT, typename ModeT, int ThreadsN>
|
||||
struct test_performance {
|
||||
static void start() {
|
||||
test_performance<AllocT, ModeT, ThreadsN - 1>::start();
|
||||
benchmark_alloc<AllocT, ModeT, ThreadsN>();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename AllocT, typename ModeT>
|
||||
struct test_performance<AllocT, ModeT, 1> {
|
||||
static void start() {
|
||||
benchmark_alloc<AllocT, ModeT, 1>();
|
||||
}
|
||||
};
|
||||
|
||||
void Unit::test_static() {
|
||||
test_performance<ipc::mem::static_alloc, alloc_FIFO , 8>::start();
|
||||
test_performance<ipc::mem::static_alloc, alloc_LIFO , 8>::start();
|
||||
test_performance<ipc::mem::static_alloc, alloc_random, 8>::start();
|
||||
}
|
||||
|
||||
void Unit::test_pool() {
|
||||
test_performance<ipc::mem::pool_alloc, alloc_FIFO , 8>::start();
|
||||
test_performance<ipc::mem::pool_alloc, alloc_LIFO , 8>::start();
|
||||
test_performance<ipc::mem::pool_alloc, alloc_random, 8>::start();
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
|
||||
#include "random.hpp"
|
||||
|
||||
#include "memory/resource.h"
|
||||
#include "pool_alloc.h"
|
||||
|
||||
#include "test.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class Unit : public TestSuite {
|
||||
Q_OBJECT
|
||||
|
||||
const char* name() const {
|
||||
return "test_mem";
|
||||
}
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
|
||||
void test_alloc_free();
|
||||
void test_static();
|
||||
void test_pool();
|
||||
} unit__;
|
||||
|
||||
#include "test_mem.moc"
|
||||
|
||||
constexpr int DataMin = sizeof(void*);
|
||||
constexpr int DataMax = sizeof(void*) * 16;
|
||||
constexpr int LoopCount = 1000000;
|
||||
|
||||
std::vector<std::size_t> sizes__;
|
||||
|
||||
template <typename M>
|
||||
struct alloc_ix_t {
|
||||
static std::vector<int> ix_[2];
|
||||
static bool inited_;
|
||||
};
|
||||
|
||||
template <typename M>
|
||||
std::vector<int> alloc_ix_t<M>::ix_[2] = { std::vector<int>(LoopCount), std::vector<int>(LoopCount) };
|
||||
template <typename M>
|
||||
bool alloc_ix_t<M>::inited_ = false;
|
||||
|
||||
struct alloc_random : alloc_ix_t<alloc_random> {
|
||||
alloc_random() {
|
||||
if (inited_) return;
|
||||
inited_ = true;
|
||||
capo::random<> rdm_index(0, LoopCount - 1);
|
||||
for (int i = 0; i < LoopCount; ++i) {
|
||||
ix_[0][static_cast<std::size_t>(i)] =
|
||||
ix_[1][static_cast<std::size_t>(i)] = rdm_index();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct alloc_LIFO : alloc_ix_t<alloc_LIFO> {
|
||||
alloc_LIFO() {
|
||||
if (inited_) return;
|
||||
inited_ = true;
|
||||
for (int i = 0, n = LoopCount - 1; i < LoopCount; ++i, --n) {
|
||||
ix_[0][static_cast<std::size_t>(i)] =
|
||||
ix_[1][static_cast<std::size_t>(n)] = i;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct alloc_FIFO : alloc_ix_t<alloc_FIFO> {
|
||||
alloc_FIFO() {
|
||||
if (inited_) return;
|
||||
inited_ = true;
|
||||
for (int i = 0; i < LoopCount; ++i) {
|
||||
ix_[0][static_cast<std::size_t>(i)] =
|
||||
ix_[1][static_cast<std::size_t>(i)] = i;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void Unit::initTestCase() {
|
||||
TestSuite::initTestCase();
|
||||
|
||||
capo::random<> rdm { DataMin, DataMax };
|
||||
for (int i = 0; i < LoopCount; ++i) {
|
||||
sizes__.emplace_back(static_cast<std::size_t>(rdm()));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename AllocT>
|
||||
void benchmark_alloc() {
|
||||
std::cout << std::endl << type_name<AllocT>() << std::endl;
|
||||
|
||||
test_stopwatch sw;
|
||||
sw.start();
|
||||
|
||||
for (std::size_t x = 0; x < 10; ++x)
|
||||
for (std::size_t n = 0; n < LoopCount; ++n) {
|
||||
std::size_t s = sizes__[n];
|
||||
AllocT::free(AllocT::alloc(s), s);
|
||||
}
|
||||
|
||||
sw.print_elapsed<1>(DataMin, DataMax, LoopCount * 10);
|
||||
}
|
||||
|
||||
void Unit::test_alloc_free() {
|
||||
benchmark_alloc<ipc::mem::static_alloc>();
|
||||
benchmark_alloc<ipc::mem::async_pool_alloc>();
|
||||
}
|
||||
|
||||
template <typename AllocT, typename ModeT, int ThreadsN>
|
||||
void benchmark_alloc() {
|
||||
std::cout << std::endl
|
||||
<< "[Threads: " << ThreadsN << ", Mode: " << type_name<ModeT>() << "] "
|
||||
<< type_name<AllocT>() << std::endl;
|
||||
|
||||
std::vector<void*> ptrs[ThreadsN];
|
||||
for (auto& vec : ptrs) {
|
||||
vec.resize(LoopCount);
|
||||
}
|
||||
ModeT mode;
|
||||
|
||||
std::atomic_int fini { 0 };
|
||||
test_stopwatch sw;
|
||||
|
||||
std::thread works[ThreadsN];
|
||||
int pid = 0;
|
||||
|
||||
for (auto& w : works) {
|
||||
w = std::thread {[&, pid] {
|
||||
sw.start();
|
||||
for (std::size_t x = 0; x < 2; ++x) {
|
||||
for(std::size_t n = 0; n < LoopCount; ++n) {
|
||||
int m = mode.ix_[x][n];
|
||||
void*& p = ptrs[pid][static_cast<std::size_t>(m)];
|
||||
std::size_t s = sizes__[static_cast<std::size_t>(m)];
|
||||
if (p == nullptr) {
|
||||
p = AllocT::alloc(s);
|
||||
}
|
||||
else {
|
||||
AllocT::free(p, s);
|
||||
p = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((fini.fetch_add(1, std::memory_order_relaxed) + 1) == ThreadsN) {
|
||||
sw.print_elapsed<1>(DataMin, DataMax, LoopCount * ThreadsN);
|
||||
}
|
||||
}};
|
||||
++pid;
|
||||
}
|
||||
sw.start();
|
||||
|
||||
for (auto& w : works) w.join();
|
||||
}
|
||||
|
||||
template <typename AllocT, typename ModeT, int ThreadsN>
|
||||
struct test_performance {
|
||||
static void start() {
|
||||
test_performance<AllocT, ModeT, ThreadsN - 1>::start();
|
||||
benchmark_alloc<AllocT, ModeT, ThreadsN>();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename AllocT, typename ModeT>
|
||||
struct test_performance<AllocT, ModeT, 1> {
|
||||
static void start() {
|
||||
benchmark_alloc<AllocT, ModeT, 1>();
|
||||
}
|
||||
};
|
||||
|
||||
void Unit::test_static() {
|
||||
test_performance<ipc::mem::static_alloc, alloc_FIFO , 8>::start();
|
||||
test_performance<ipc::mem::static_alloc, alloc_LIFO , 8>::start();
|
||||
test_performance<ipc::mem::static_alloc, alloc_random, 8>::start();
|
||||
}
|
||||
|
||||
void Unit::test_pool() {
|
||||
// test_performance<ipc::mem::pool_alloc, alloc_FIFO , 8>::start();
|
||||
// for (;;) {
|
||||
test_performance<ipc::mem::pool_alloc, alloc_FIFO , 8>::start();
|
||||
test_performance<ipc::mem::pool_alloc, alloc_LIFO , 8>::start();
|
||||
test_performance<ipc::mem::pool_alloc, alloc_random, 8>::start();
|
||||
// }
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
@ -1,114 +1,114 @@
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <thread>
|
||||
|
||||
#include "shm.h"
|
||||
#include "test.h"
|
||||
|
||||
using namespace ipc::shm;
|
||||
|
||||
namespace {
|
||||
|
||||
class Unit : public TestSuite {
|
||||
Q_OBJECT
|
||||
|
||||
const char* name() const {
|
||||
return "test_shm";
|
||||
}
|
||||
|
||||
private slots:
|
||||
void cleanupTestCase();
|
||||
|
||||
void test_acquire();
|
||||
void test_release();
|
||||
void test_get();
|
||||
void test_hello();
|
||||
void test_mt();
|
||||
} unit__;
|
||||
|
||||
#include "test_shm.moc"
|
||||
|
||||
handle shm_hd__;
|
||||
|
||||
void Unit::cleanupTestCase() {
|
||||
shm_hd__.release();
|
||||
}
|
||||
|
||||
void Unit::test_acquire() {
|
||||
QVERIFY(!shm_hd__.valid());
|
||||
|
||||
QVERIFY(shm_hd__.acquire("my-test-1", 1024));
|
||||
QVERIFY(shm_hd__.valid());
|
||||
QCOMPARE(shm_hd__.name(), "my-test-1");
|
||||
|
||||
QVERIFY(shm_hd__.acquire("my-test-2", 2048));
|
||||
QVERIFY(shm_hd__.valid());
|
||||
QCOMPARE(shm_hd__.name(), "my-test-2");
|
||||
|
||||
QVERIFY(shm_hd__.acquire("my-test-3", 4096));
|
||||
QVERIFY(shm_hd__.valid());
|
||||
QCOMPARE(shm_hd__.name(), "my-test-3");
|
||||
}
|
||||
|
||||
void Unit::test_release() {
|
||||
QVERIFY(shm_hd__.valid());
|
||||
shm_hd__.release();
|
||||
QVERIFY(!shm_hd__.valid());
|
||||
}
|
||||
|
||||
void Unit::test_get() {
|
||||
QVERIFY(shm_hd__.get() == nullptr);
|
||||
QVERIFY(shm_hd__.acquire("my-test", 1024));
|
||||
|
||||
auto mem = shm_hd__.get();
|
||||
QVERIFY(mem != nullptr);
|
||||
QVERIFY(mem == shm_hd__.get());
|
||||
|
||||
std::uint8_t buf[1024] = {};
|
||||
QVERIFY(memcmp(mem, buf, sizeof(buf)) == 0);
|
||||
|
||||
handle shm_other(shm_hd__.name(), shm_hd__.size());
|
||||
QVERIFY(shm_other.get() != shm_hd__.get());
|
||||
}
|
||||
|
||||
void Unit::test_hello() {
|
||||
auto mem = shm_hd__.get();
|
||||
QVERIFY(mem != nullptr);
|
||||
|
||||
constexpr char hello[] = "hello!";
|
||||
std::memcpy(mem, hello, sizeof(hello));
|
||||
QCOMPARE((char*)shm_hd__.get(), hello);
|
||||
|
||||
shm_hd__.release();
|
||||
QVERIFY(shm_hd__.get() == nullptr);
|
||||
QVERIFY(shm_hd__.acquire("my-test", 1024));
|
||||
|
||||
mem = shm_hd__.get();
|
||||
QVERIFY(mem != nullptr);
|
||||
std::uint8_t buf[1024] = {};
|
||||
QVERIFY(memcmp(mem, buf, sizeof(buf)) == 0);
|
||||
|
||||
std::memcpy(mem, hello, sizeof(hello));
|
||||
QCOMPARE((char*)shm_hd__.get(), hello);
|
||||
}
|
||||
|
||||
void Unit::test_mt() {
|
||||
std::thread {
|
||||
[] {
|
||||
handle shm_mt(shm_hd__.name(), shm_hd__.size());
|
||||
|
||||
shm_hd__.release();
|
||||
|
||||
constexpr char hello[] = "hello!";
|
||||
QCOMPARE((char*)shm_mt.get(), hello);
|
||||
}
|
||||
}.join();
|
||||
QVERIFY(shm_hd__.get() == nullptr);
|
||||
QVERIFY(!shm_hd__.valid());
|
||||
|
||||
QVERIFY(shm_hd__.acquire("my-test", 1024));
|
||||
std::uint8_t buf[1024] = {};
|
||||
QVERIFY(memcmp(shm_hd__.get(), buf, sizeof(buf)) == 0);
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <thread>
|
||||
|
||||
#include "shm.h"
|
||||
#include "test.h"
|
||||
|
||||
using namespace ipc::shm;
|
||||
|
||||
namespace {
|
||||
|
||||
class Unit : public TestSuite {
|
||||
Q_OBJECT
|
||||
|
||||
const char* name() const {
|
||||
return "test_shm";
|
||||
}
|
||||
|
||||
private slots:
|
||||
void cleanupTestCase();
|
||||
|
||||
void test_acquire();
|
||||
void test_release();
|
||||
void test_get();
|
||||
void test_hello();
|
||||
void test_mt();
|
||||
} unit__;
|
||||
|
||||
#include "test_shm.moc"
|
||||
|
||||
handle shm_hd__;
|
||||
|
||||
void Unit::cleanupTestCase() {
|
||||
shm_hd__.release();
|
||||
}
|
||||
|
||||
void Unit::test_acquire() {
|
||||
QVERIFY(!shm_hd__.valid());
|
||||
|
||||
QVERIFY(shm_hd__.acquire("my-test-1", 1024));
|
||||
QVERIFY(shm_hd__.valid());
|
||||
QCOMPARE(shm_hd__.name(), "my-test-1");
|
||||
|
||||
QVERIFY(shm_hd__.acquire("my-test-2", 2048));
|
||||
QVERIFY(shm_hd__.valid());
|
||||
QCOMPARE(shm_hd__.name(), "my-test-2");
|
||||
|
||||
QVERIFY(shm_hd__.acquire("my-test-3", 4096));
|
||||
QVERIFY(shm_hd__.valid());
|
||||
QCOMPARE(shm_hd__.name(), "my-test-3");
|
||||
}
|
||||
|
||||
void Unit::test_release() {
|
||||
QVERIFY(shm_hd__.valid());
|
||||
shm_hd__.release();
|
||||
QVERIFY(!shm_hd__.valid());
|
||||
}
|
||||
|
||||
void Unit::test_get() {
|
||||
QVERIFY(shm_hd__.get() == nullptr);
|
||||
QVERIFY(shm_hd__.acquire("my-test", 1024));
|
||||
|
||||
auto mem = shm_hd__.get();
|
||||
QVERIFY(mem != nullptr);
|
||||
QVERIFY(mem == shm_hd__.get());
|
||||
|
||||
std::uint8_t buf[1024] = {};
|
||||
QVERIFY(memcmp(mem, buf, sizeof(buf)) == 0);
|
||||
|
||||
handle shm_other(shm_hd__.name(), shm_hd__.size());
|
||||
QVERIFY(shm_other.get() != shm_hd__.get());
|
||||
}
|
||||
|
||||
void Unit::test_hello() {
|
||||
auto mem = shm_hd__.get();
|
||||
QVERIFY(mem != nullptr);
|
||||
|
||||
constexpr char hello[] = "hello!";
|
||||
std::memcpy(mem, hello, sizeof(hello));
|
||||
QCOMPARE((char*)shm_hd__.get(), hello);
|
||||
|
||||
shm_hd__.release();
|
||||
QVERIFY(shm_hd__.get() == nullptr);
|
||||
QVERIFY(shm_hd__.acquire("my-test", 1024));
|
||||
|
||||
mem = shm_hd__.get();
|
||||
QVERIFY(mem != nullptr);
|
||||
std::uint8_t buf[1024] = {};
|
||||
QVERIFY(memcmp(mem, buf, sizeof(buf)) == 0);
|
||||
|
||||
std::memcpy(mem, hello, sizeof(hello));
|
||||
QCOMPARE((char*)shm_hd__.get(), hello);
|
||||
}
|
||||
|
||||
void Unit::test_mt() {
|
||||
std::thread {
|
||||
[] {
|
||||
handle shm_mt(shm_hd__.name(), shm_hd__.size());
|
||||
|
||||
shm_hd__.release();
|
||||
|
||||
constexpr char hello[] = "hello!";
|
||||
QCOMPARE((char*)shm_mt.get(), hello);
|
||||
}
|
||||
}.join();
|
||||
QVERIFY(shm_hd__.get() == nullptr);
|
||||
QVERIFY(!shm_hd__.valid());
|
||||
|
||||
QVERIFY(shm_hd__.acquire("my-test", 1024));
|
||||
std::uint8_t buf[1024] = {};
|
||||
QVERIFY(memcmp(shm_hd__.get(), buf, sizeof(buf)) == 0);
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
@ -1,46 +1,46 @@
|
||||
#include <thread>
|
||||
#include <iostream>
|
||||
|
||||
#include "platform/waiter_wrapper.h"
|
||||
#include "test.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class Unit : public TestSuite {
|
||||
Q_OBJECT
|
||||
|
||||
const char* name() const {
|
||||
return "test_waiter";
|
||||
}
|
||||
|
||||
private slots:
|
||||
void test_broadcast();
|
||||
} unit__;
|
||||
|
||||
#include "test_waiter.moc"
|
||||
|
||||
void Unit::test_broadcast() {
|
||||
ipc::detail::waiter w;
|
||||
std::thread ts[10];
|
||||
|
||||
for (auto& t : ts) {
|
||||
t = std::thread([&w] {
|
||||
ipc::detail::waiter_wrapper wp { &w };
|
||||
QVERIFY(wp.open("test-ipc-waiter"));
|
||||
QVERIFY(wp.wait_if([] { return true; }));
|
||||
wp.close();
|
||||
});
|
||||
}
|
||||
|
||||
ipc::detail::waiter_wrapper wp { &w };
|
||||
QVERIFY(wp.open("test-ipc-waiter"));
|
||||
|
||||
std::cout << "waiting for broadcast...\n";
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
QVERIFY(wp.broadcast());
|
||||
|
||||
for (auto& t : ts) t.join();
|
||||
wp.close();
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
#include <thread>
|
||||
#include <iostream>
|
||||
|
||||
#include "platform/waiter_wrapper.h"
|
||||
#include "test.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class Unit : public TestSuite {
|
||||
Q_OBJECT
|
||||
|
||||
const char* name() const {
|
||||
return "test_waiter";
|
||||
}
|
||||
|
||||
private slots:
|
||||
void test_broadcast();
|
||||
} unit__;
|
||||
|
||||
#include "test_waiter.moc"
|
||||
|
||||
void Unit::test_broadcast() {
|
||||
ipc::detail::waiter w;
|
||||
std::thread ts[10];
|
||||
|
||||
for (auto& t : ts) {
|
||||
t = std::thread([&w] {
|
||||
ipc::detail::waiter_wrapper wp { &w };
|
||||
QVERIFY(wp.open("test-ipc-waiter"));
|
||||
QVERIFY(wp.wait_if([] { return true; }));
|
||||
wp.close();
|
||||
});
|
||||
}
|
||||
|
||||
ipc::detail::waiter_wrapper wp { &w };
|
||||
QVERIFY(wp.open("test-ipc-waiter"));
|
||||
|
||||
std::cout << "waiting for broadcast...\n";
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
QVERIFY(wp.broadcast());
|
||||
|
||||
for (auto& t : ts) t.join();
|
||||
wp.close();
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user