mirror of
https://github.com/mutouyun/cpp-ipc.git
synced 2025-12-07 01:06:45 +08:00
开始重构
This commit is contained in:
parent
20168fb869
commit
f18c27ec29
@ -13,6 +13,8 @@ if(NOT MSVC)
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
|
||||
endif()
|
||||
|
||||
add_definitions(-DLIBIPC_CPP_14 -DLIBIPC_CPP_17)
|
||||
|
||||
if (MSVC)
|
||||
set(CompilerFlags
|
||||
CMAKE_CXX_FLAGS
|
||||
@ -55,11 +57,11 @@ if (LIBIPC_BUILD_TESTS)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
||||
if (LIBIPC_BUILD_DEMOS)
|
||||
add_subdirectory(demo/chat)
|
||||
add_subdirectory(demo/msg_que)
|
||||
add_subdirectory(demo/send_recv)
|
||||
endif()
|
||||
# if (LIBIPC_BUILD_DEMOS)
|
||||
# add_subdirectory(demo/chat)
|
||||
# add_subdirectory(demo/msg_que)
|
||||
# add_subdirectory(demo/send_recv)
|
||||
# endif()
|
||||
|
||||
install(
|
||||
DIRECTORY "include/"
|
||||
|
||||
@ -1,68 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include <type_traits>
|
||||
|
||||
#include "libipc/export.h"
|
||||
#include "libipc/def.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
class IPC_EXPORT buffer {
|
||||
public:
|
||||
using destructor_t = void (*)(void*, std::size_t);
|
||||
|
||||
buffer();
|
||||
|
||||
buffer(void* p, std::size_t s, destructor_t d);
|
||||
buffer(void* p, std::size_t s, destructor_t d, void* additional);
|
||||
buffer(void* p, std::size_t s);
|
||||
|
||||
template <std::size_t N>
|
||||
explicit buffer(byte_t const (& data)[N])
|
||||
: buffer(data, sizeof(data)) {
|
||||
}
|
||||
explicit buffer(char const & c);
|
||||
|
||||
buffer(buffer&& rhs);
|
||||
~buffer();
|
||||
|
||||
void swap(buffer& rhs);
|
||||
buffer& operator=(buffer rhs);
|
||||
|
||||
bool empty() const noexcept;
|
||||
|
||||
void * data() noexcept;
|
||||
void const * data() const noexcept;
|
||||
|
||||
template <typename T>
|
||||
T get() const { return T(data()); }
|
||||
|
||||
std::size_t size() const noexcept;
|
||||
|
||||
std::tuple<void*, std::size_t> to_tuple() {
|
||||
return std::make_tuple(data(), size());
|
||||
}
|
||||
|
||||
std::tuple<void const *, std::size_t> to_tuple() const {
|
||||
return std::make_tuple(data(), size());
|
||||
}
|
||||
|
||||
std::vector<byte_t> to_vector() const {
|
||||
return {
|
||||
get<byte_t const *>(),
|
||||
get<byte_t const *>() + size()
|
||||
};
|
||||
}
|
||||
|
||||
friend IPC_EXPORT bool operator==(buffer const & b1, buffer const & b2);
|
||||
friend IPC_EXPORT bool operator!=(buffer const & b1, buffer const & b2);
|
||||
|
||||
private:
|
||||
class buffer_;
|
||||
buffer_* p_;
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint> // std::uint64_t
|
||||
|
||||
#include "libipc/export.h"
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/mutex.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace sync {
|
||||
|
||||
class IPC_EXPORT condition {
|
||||
condition(condition const &) = delete;
|
||||
condition &operator=(condition const &) = delete;
|
||||
|
||||
public:
|
||||
condition();
|
||||
explicit condition(char const *name);
|
||||
~condition();
|
||||
|
||||
void const *native() const noexcept;
|
||||
void *native() noexcept;
|
||||
|
||||
bool valid() const noexcept;
|
||||
|
||||
bool open(char const *name) noexcept;
|
||||
void close() noexcept;
|
||||
|
||||
bool wait(ipc::sync::mutex &mtx, std::uint64_t tm = ipc::invalid_value) noexcept;
|
||||
bool notify(ipc::sync::mutex &mtx) noexcept;
|
||||
bool broadcast(ipc::sync::mutex &mtx) noexcept;
|
||||
|
||||
private:
|
||||
class condition_;
|
||||
condition_* p_;
|
||||
};
|
||||
|
||||
} // namespace sync
|
||||
} // namespace ipc
|
||||
@ -1,68 +1,19 @@
|
||||
/**
|
||||
* @file def.h
|
||||
* @author mutouyun (orz@orzz.org)
|
||||
* @brief Define the trivial configuration information
|
||||
* @date 2022-02-27
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <new>
|
||||
#include <utility>
|
||||
|
||||
namespace ipc {
|
||||
#define LIBIPC_NAMESPACE_BEG_ namespace ipc {
|
||||
#define LIBIPC_NAMESPACE_END_ }
|
||||
|
||||
// types
|
||||
|
||||
using byte_t = std::uint8_t;
|
||||
|
||||
template <std::size_t N>
|
||||
struct uint;
|
||||
|
||||
template <> struct uint<8 > { using type = std::uint8_t ; };
|
||||
template <> struct uint<16> { using type = std::uint16_t; };
|
||||
template <> struct uint<32> { using type = std::uint32_t; };
|
||||
template <> struct uint<64> { using type = std::uint64_t; };
|
||||
|
||||
template <std::size_t N>
|
||||
using uint_t = typename uint<N>::type;
|
||||
LIBIPC_NAMESPACE_BEG_
|
||||
|
||||
// constants
|
||||
|
||||
enum : std::uint32_t {
|
||||
invalid_value = (std::numeric_limits<std::uint32_t>::max)(),
|
||||
default_timeout = 100, // ms
|
||||
};
|
||||
|
||||
enum : std::size_t {
|
||||
data_length = 64,
|
||||
large_msg_limit = data_length,
|
||||
large_msg_align = 1024,
|
||||
large_msg_cache = 32,
|
||||
};
|
||||
|
||||
enum class relat { // multiplicity of the relationship
|
||||
single,
|
||||
multi
|
||||
};
|
||||
|
||||
enum class trans { // transmission
|
||||
unicast,
|
||||
broadcast
|
||||
};
|
||||
|
||||
// producer-consumer policy flag
|
||||
|
||||
template <relat Rp, relat Rc, trans Ts>
|
||||
struct wr {};
|
||||
|
||||
template <typename WR>
|
||||
struct relat_trait;
|
||||
|
||||
template <relat Rp, relat Rc, trans Ts>
|
||||
struct relat_trait<wr<Rp, Rc, Ts>> {
|
||||
constexpr static bool is_multi_producer = (Rp == relat::multi);
|
||||
constexpr static bool is_multi_consumer = (Rc == relat::multi);
|
||||
constexpr static bool is_broadcast = (Ts == trans::broadcast);
|
||||
};
|
||||
|
||||
template <template <typename> class Policy, typename Flag>
|
||||
struct relat_trait<Policy<Flag>> : relat_trait<Flag> {};
|
||||
|
||||
} // namespace ipc
|
||||
LIBIPC_NAMESPACE_END_
|
||||
|
||||
155
include/libipc/detect_plat.h
Normal file
155
include/libipc/detect_plat.h
Normal file
@ -0,0 +1,155 @@
|
||||
/**
|
||||
* @file detect_plat.h
|
||||
* @author mutouyun (orz@orzz.org)
|
||||
* @brief Define platform detection related interfaces
|
||||
* @date 2022-02-27
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// OS
|
||||
|
||||
#if defined(WINCE) || defined(_WIN32_WCE)
|
||||
# define LIBIPC_OS_WINCE
|
||||
#elif defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || \
|
||||
(defined(__x86_64) && defined(__MSYS__))
|
||||
#define LIBIPC_OS_WIN64
|
||||
#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || \
|
||||
defined(__NT__) || defined(__MSYS__)
|
||||
# define LIBIPC_OS_WIN32
|
||||
#elif defined(__linux__) || defined(__linux)
|
||||
# define LIBIPC_OS_LINUX
|
||||
#elif defined(__QNX__) || defined(__QNXNTO__)
|
||||
# define LIBIPC_OS_QNX
|
||||
#elif defined(ANDROID) || defined(__ANDROID__)
|
||||
# define LIBIPC_OS_ANDROID
|
||||
#else
|
||||
# error "This OS is unsupported."
|
||||
#endif
|
||||
|
||||
#if defined(LIBIPC_OS_WIN32) || defined(LIBIPC_OS_WIN64) || \
|
||||
defined(LIBIPC_OS_WINCE)
|
||||
# define LIBIPC_OS_WIN
|
||||
#endif
|
||||
|
||||
// Compiler
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# define LIBIPC_CC_MSVC
|
||||
#elif defined(__GNUC__)
|
||||
# define LIBIPC_CC_GNUC
|
||||
#else
|
||||
# error "This compiler is unsupported."
|
||||
#endif
|
||||
|
||||
// Instruction set
|
||||
// @see https://sourceforge.net/p/predef/wiki/Architectures/
|
||||
|
||||
#if defined(_M_X64) || defined(_M_AMD64) || \
|
||||
defined(__x86_64__) || defined(__x86_64) || \
|
||||
defined(__amd64__) || defined(__amd64)
|
||||
# define LIBIPC_INSTR_X64
|
||||
#elif defined(_M_IA64) || defined(__IA64__) || defined(_IA64) || \
|
||||
defined(__ia64__) || defined(__ia64)
|
||||
# define LIBIPC_INSTR_I64
|
||||
#elif defined(_M_IX86) || defined(_X86_) || defined(__i386__) || defined(__i386)
|
||||
# define LIBIPC_INSTR_X86
|
||||
#elif defined(_M_ARM64) || defined(__arm64__) || defined(__aarch64__)
|
||||
# define LIBIPC_INSTR_ARM64
|
||||
#elif defined(_M_ARM) || defined(_ARM) || defined(__arm__) || defined(__arm)
|
||||
# define LIBIPC_INSTR_ARM32
|
||||
#else
|
||||
# error "This instruction set is unsupported."
|
||||
#endif
|
||||
|
||||
#if defined(LIBIPC_INSTR_X86) || defined(LIBIPC_INSTR_X64)
|
||||
# define LIBIPC_INSTR_X86_64
|
||||
#elif defined(LIBIPC_INSTR_ARM32) || defined(LIBIPC_INSTR_ARM64)
|
||||
# define LIBIPC_INSTR_ARM
|
||||
#endif
|
||||
|
||||
// Byte order
|
||||
|
||||
#if defined(__BYTE_ORDER__)
|
||||
# define LIBIPC_ENDIAN_BIG (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
|
||||
# define LIBIPC_ENDIAN_LIT (!LIBIPC_ENDIAN_BIG)
|
||||
#else
|
||||
# define LIBIPC_ENDIAN_BIG (0)
|
||||
# define LIBIPC_ENDIAN_LIT (1)
|
||||
#endif
|
||||
|
||||
// C++ version
|
||||
|
||||
#if (__cplusplus >= 202002L) && !defined(LIBIPC_CPP_20)
|
||||
# define LIBIPC_CPP_20
|
||||
#endif
|
||||
#if (__cplusplus >= 201703L) && !defined(LIBIPC_CPP_17)
|
||||
# define LIBIPC_CPP_17
|
||||
#endif
|
||||
#if (__cplusplus >= 201402L) && !defined(LIBIPC_CPP_14)
|
||||
# define LIBIPC_CPP_14
|
||||
#endif
|
||||
|
||||
#if !defined(LIBIPC_CPP_20) && \
|
||||
!defined(LIBIPC_CPP_17) && \
|
||||
!defined(LIBIPC_CPP_14)
|
||||
# error "This C++ version is unsupported."
|
||||
#endif
|
||||
|
||||
// C++ attributes
|
||||
|
||||
#if defined(__has_cpp_attribute)
|
||||
# if __has_cpp_attribute(fallthrough)
|
||||
# define LIBIPC_FALLTHROUGH [[fallthrough]]
|
||||
# endif
|
||||
# if __has_cpp_attribute(maybe_unused)
|
||||
# define LIBIPC_UNUSED [[maybe_unused]]
|
||||
# endif
|
||||
# if __has_cpp_attribute(likely)
|
||||
# define LIBIPC_LIKELY(...) (__VA_ARGS__) [[likely]]
|
||||
# endif
|
||||
# if __has_cpp_attribute(unlikely)
|
||||
# define LIBIPC_UNLIKELY(...) (__VA_ARGS__) [[unlikely]]
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if !defined(LIBIPC_FALLTHROUGH)
|
||||
# if defined(LIBIPC_CC_GNUC)
|
||||
# define LIBIPC_FALLTHROUGH __attribute__((__fallthrough__))
|
||||
# else
|
||||
# define LIBIPC_FALLTHROUGH
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if !defined(LIBIPC_UNUSED)
|
||||
# if defined(LIBIPC_CC_GNUC)
|
||||
# define LIBIPC_UNUSED __attribute__((__unused__))
|
||||
# elif defined(LIBIPC_CC_MSVC_)
|
||||
# define LIBIPC_UNUSED __pragma(warning(suppress: 4100 4101 4189))
|
||||
# else
|
||||
# define LIBIPC_UNUSED
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if !defined(LIBIPC_LIKELY)
|
||||
# if defined(__has_builtin)
|
||||
# if __has_builtin(__builtin_expect)
|
||||
# define LIBIPC_LIKELY(...) (__builtin_expect(!!(__VA_ARGS__), 1))
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if !defined(LIBIPC_LIKELY)
|
||||
# define LIBIPC_LIKELY(...) (__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#if !defined(LIBIPC_UNLIKELY)
|
||||
# if defined(__has_builtin)
|
||||
# if __has_builtin(__builtin_expect)
|
||||
# define LIBIPC_UNLIKELY(...) (__builtin_expect(!!(__VA_ARGS__), 0))
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if !defined(LIBIPC_UNLIKELY)
|
||||
# define LIBIPC_UNLIKELY(...) (__VA_ARGS__)
|
||||
#endif
|
||||
@ -1,54 +1,46 @@
|
||||
/**
|
||||
* @file export.h
|
||||
* @author mutouyun (orz@orzz.org)
|
||||
* @brief Define the symbol export interfaces
|
||||
* @date 2022-02-27
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "libipc/detect_plat.h"
|
||||
|
||||
#if defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
|
||||
|
||||
# define IPC_DECL_EXPORT Q_DECL_EXPORT
|
||||
# define IPC_DECL_IMPORT Q_DECL_IMPORT
|
||||
# define LIBIPC_DECL_EXPORT Q_DECL_EXPORT
|
||||
# define LIBIPC_DECL_IMPORT Q_DECL_IMPORT
|
||||
|
||||
#else // defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
|
||||
|
||||
/*
|
||||
* Compiler & system detection for IPC_DECL_EXPORT & IPC_DECL_IMPORT.
|
||||
* Compiler & system detection for LIBIPC_DECL_EXPORT & LIBIPC_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")))
|
||||
# if defined(LIBIPC_CC_MSVC) || defined(LIBIPC_OS_WIN)
|
||||
# define LIBIPC_DECL_EXPORT __declspec(dllexport)
|
||||
# define LIBIPC_DECL_IMPORT __declspec(dllimport)
|
||||
# elif defined(LIBIPC_OS_ANDROID) || defined(LIBIPC_OS_LINUX) || defined(LIBIPC_CC_GNUC)
|
||||
# define LIBIPC_DECL_EXPORT __attribute__((visibility("default")))
|
||||
# define LIBIPC_DECL_IMPORT __attribute__((visibility("default")))
|
||||
# else
|
||||
# define IPC_DECL_EXPORT __declspec(dllexport)
|
||||
# define IPC_DECL_IMPORT __declspec(dllimport)
|
||||
# define LIBIPC_DECL_EXPORT __attribute__((visibility("default")))
|
||||
# define LIBIPC_DECL_IMPORT __attribute__((visibility("default")))
|
||||
# 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.
|
||||
* Define LIBIPC_EXPORT for exporting function & class.
|
||||
*/
|
||||
|
||||
#ifndef IPC_EXPORT
|
||||
#if defined(LIBIPC_LIBRARY_SHARED_BUILDING__)
|
||||
# define IPC_EXPORT IPC_DECL_EXPORT
|
||||
#elif defined(LIBIPC_LIBRARY_SHARED_USING__)
|
||||
# define IPC_EXPORT IPC_DECL_IMPORT
|
||||
#else
|
||||
# define IPC_EXPORT
|
||||
#endif
|
||||
#endif /*IPC_EXPORT*/
|
||||
#ifndef LIBIPC_EXPORT
|
||||
# if defined(LIBIPC_LIBRARY_SHARED_BUILDING__)
|
||||
# define LIBIPC_EXPORT LIBIPC_DECL_EXPORT
|
||||
# elif defined(LIBIPC_LIBRARY_SHARED_USING__)
|
||||
# define LIBIPC_EXPORT LIBIPC_DECL_IMPORT
|
||||
# else
|
||||
# define LIBIPC_EXPORT
|
||||
# endif
|
||||
#endif /*LIBIPC_EXPORT*/
|
||||
|
||||
@ -1,173 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "libipc/export.h"
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/buffer.h"
|
||||
#include "libipc/shm.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
using handle_t = void*;
|
||||
using buff_t = buffer;
|
||||
|
||||
enum : unsigned {
|
||||
sender,
|
||||
receiver
|
||||
};
|
||||
|
||||
template <typename Flag>
|
||||
struct IPC_EXPORT chan_impl {
|
||||
static bool connect (ipc::handle_t * ph, char const * name, unsigned mode);
|
||||
static bool reconnect (ipc::handle_t * ph, unsigned mode);
|
||||
static void disconnect(ipc::handle_t h);
|
||||
static void destroy (ipc::handle_t h);
|
||||
|
||||
static char const * name(ipc::handle_t h);
|
||||
|
||||
static std::size_t recv_count(ipc::handle_t h);
|
||||
static bool wait_for_recv(ipc::handle_t h, std::size_t r_count, std::uint64_t tm);
|
||||
|
||||
static bool send(ipc::handle_t h, void const * data, std::size_t size, std::uint64_t tm);
|
||||
static buff_t recv(ipc::handle_t h, std::uint64_t tm);
|
||||
static buff_t try_recv(ipc::handle_t h);
|
||||
};
|
||||
|
||||
template <typename Flag>
|
||||
class chan_wrapper {
|
||||
private:
|
||||
using detail_t = chan_impl<Flag>;
|
||||
|
||||
ipc::handle_t h_ = nullptr;
|
||||
unsigned mode_ = ipc::sender;
|
||||
bool connected_ = false;
|
||||
|
||||
public:
|
||||
chan_wrapper() noexcept = default;
|
||||
|
||||
explicit chan_wrapper(char const * name, unsigned mode = ipc::sender)
|
||||
: connected_{this->connect(name, mode)} {
|
||||
}
|
||||
|
||||
chan_wrapper(chan_wrapper&& rhs) noexcept
|
||||
: chan_wrapper{} {
|
||||
swap(rhs);
|
||||
}
|
||||
|
||||
~chan_wrapper() {
|
||||
detail_t::destroy(h_);
|
||||
}
|
||||
|
||||
void swap(chan_wrapper& rhs) noexcept {
|
||||
std::swap(h_ , rhs.h_);
|
||||
std::swap(mode_ , rhs.mode_);
|
||||
std::swap(connected_, rhs.connected_);
|
||||
}
|
||||
|
||||
chan_wrapper& operator=(chan_wrapper rhs) noexcept {
|
||||
swap(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
char const * name() const noexcept {
|
||||
return detail_t::name(h_);
|
||||
}
|
||||
|
||||
ipc::handle_t handle() const noexcept {
|
||||
return h_;
|
||||
}
|
||||
|
||||
bool valid() const noexcept {
|
||||
return (handle() != nullptr);
|
||||
}
|
||||
|
||||
unsigned mode() const noexcept {
|
||||
return mode_;
|
||||
}
|
||||
|
||||
chan_wrapper clone() const {
|
||||
return chan_wrapper {name(), mode_};
|
||||
}
|
||||
|
||||
/**
|
||||
* Building handle, then try connecting with name & mode flags.
|
||||
*/
|
||||
bool connect(char const * name, unsigned mode = ipc::sender | ipc::receiver) {
|
||||
if (name == nullptr || name[0] == '\0') return false;
|
||||
detail_t::disconnect(h_); // clear old connection
|
||||
return connected_ = detail_t::connect(&h_, name, mode_ = mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try connecting with new mode flags.
|
||||
*/
|
||||
bool reconnect(unsigned mode) {
|
||||
if (!valid()) return false;
|
||||
if (connected_ && (mode_ == mode)) return true;
|
||||
return connected_ = detail_t::reconnect(&h_, mode_ = mode);
|
||||
}
|
||||
|
||||
void disconnect() {
|
||||
if (!valid()) return;
|
||||
detail_t::disconnect(h_);
|
||||
connected_ = false;
|
||||
}
|
||||
|
||||
std::size_t recv_count() const {
|
||||
return detail_t::recv_count(h_);
|
||||
}
|
||||
|
||||
bool wait_for_recv(std::size_t r_count, std::uint64_t tm = invalid_value) const {
|
||||
return detail_t::wait_for_recv(h_, r_count, tm);
|
||||
}
|
||||
|
||||
static bool wait_for_recv(char const * name, std::size_t r_count, std::uint64_t tm = invalid_value) {
|
||||
return chan_wrapper(name).wait_for_recv(r_count, tm);
|
||||
}
|
||||
|
||||
bool send(void const * data, std::size_t size, std::uint64_t tm = default_timeout) {
|
||||
return detail_t::send(h_, data, size, tm);
|
||||
}
|
||||
bool send(buff_t const & buff, std::uint64_t tm = default_timeout) {
|
||||
return this->send(buff.data(), buff.size(), tm);
|
||||
}
|
||||
bool send(std::string const & str, std::uint64_t tm = default_timeout) {
|
||||
return this->send(str.c_str(), str.size() + 1, tm);
|
||||
}
|
||||
|
||||
buff_t recv(std::uint64_t tm = invalid_value) {
|
||||
return detail_t::recv(h_, tm);
|
||||
}
|
||||
buff_t try_recv() {
|
||||
return detail_t::try_recv(h_);
|
||||
}
|
||||
};
|
||||
|
||||
template <relat Rp, relat Rc, trans Ts>
|
||||
using chan = chan_wrapper<ipc::wr<Rp, Rc, Ts>>;
|
||||
|
||||
/**
|
||||
* 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<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<relat::multi, relat::multi, trans::broadcast>;
|
||||
|
||||
} // namespace ipc
|
||||
@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint> // std::uint64_t
|
||||
#include <system_error>
|
||||
|
||||
#include "libipc/export.h"
|
||||
#include "libipc/def.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace sync {
|
||||
|
||||
class IPC_EXPORT mutex {
|
||||
mutex(mutex const &) = delete;
|
||||
mutex &operator=(mutex const &) = delete;
|
||||
|
||||
public:
|
||||
mutex();
|
||||
explicit mutex(char const *name);
|
||||
~mutex();
|
||||
|
||||
void const *native() const noexcept;
|
||||
void *native() noexcept;
|
||||
|
||||
bool valid() const noexcept;
|
||||
|
||||
bool open(char const *name) noexcept;
|
||||
void close() noexcept;
|
||||
|
||||
bool lock(std::uint64_t tm = ipc::invalid_value) noexcept;
|
||||
bool try_lock() noexcept(false); // std::system_error
|
||||
bool unlock() noexcept;
|
||||
|
||||
private:
|
||||
class mutex_;
|
||||
mutex_* p_;
|
||||
};
|
||||
|
||||
} // namespace sync
|
||||
} // namespace ipc
|
||||
@ -1,103 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <new>
|
||||
#include <utility>
|
||||
|
||||
#include "libipc/export.h"
|
||||
#include "libipc/def.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
class IPC_EXPORT pool_alloc {
|
||||
public:
|
||||
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) {
|
||||
if (p == nullptr) return;
|
||||
destruct(p);
|
||||
pool_alloc::free(p, sizeof(T));
|
||||
}
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
@ -1,171 +0,0 @@
|
||||
#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
|
||||
if (k < 32) { std::this_thread::yield(); }
|
||||
else {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
return;
|
||||
}
|
||||
++k;
|
||||
}
|
||||
|
||||
template <std::size_t N = 32, typename K, typename F>
|
||||
inline void sleep(K& k, F&& f) {
|
||||
if (k < static_cast<K>(N)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
else {
|
||||
static_cast<void>(std::forward<F>(f)());
|
||||
return;
|
||||
}
|
||||
++k;
|
||||
}
|
||||
|
||||
template <std::size_t N = 32, typename K>
|
||||
inline void sleep(K& k) {
|
||||
sleep<N>(k, [] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
});
|
||||
}
|
||||
|
||||
} // 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
|
||||
@ -1,37 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint> // std::uint64_t
|
||||
|
||||
#include "libipc/export.h"
|
||||
#include "libipc/def.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace sync {
|
||||
|
||||
class IPC_EXPORT semaphore {
|
||||
semaphore(semaphore const &) = delete;
|
||||
semaphore &operator=(semaphore const &) = delete;
|
||||
|
||||
public:
|
||||
semaphore();
|
||||
explicit semaphore(char const *name, std::uint32_t count = 0);
|
||||
~semaphore();
|
||||
|
||||
void const *native() const noexcept;
|
||||
void *native() noexcept;
|
||||
|
||||
bool valid() const noexcept;
|
||||
|
||||
bool open(char const *name, std::uint32_t count = 0) noexcept;
|
||||
void close() noexcept;
|
||||
|
||||
bool wait(std::uint64_t tm = ipc::invalid_value) noexcept;
|
||||
bool post(std::uint32_t count = 1) noexcept;
|
||||
|
||||
private:
|
||||
class semaphore_;
|
||||
semaphore_* p_;
|
||||
};
|
||||
|
||||
} // namespace sync
|
||||
} // namespace ipc
|
||||
@ -1,59 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "libipc/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 std::int32_t release(id_t id);
|
||||
IPC_EXPORT void remove (id_t id);
|
||||
IPC_EXPORT void remove (char const * name);
|
||||
|
||||
IPC_EXPORT std::int32_t get_ref(id_t id);
|
||||
IPC_EXPORT void sub_ref(id_t id);
|
||||
|
||||
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 noexcept;
|
||||
std::size_t size () const noexcept;
|
||||
char const * name () const noexcept;
|
||||
|
||||
std::int32_t ref() const noexcept;
|
||||
void sub_ref() noexcept;
|
||||
|
||||
bool acquire(char const * name, std::size_t size, unsigned mode = create | open);
|
||||
std::int32_t release();
|
||||
|
||||
void* get() const;
|
||||
|
||||
void attach(id_t);
|
||||
id_t detach();
|
||||
|
||||
private:
|
||||
class handle_;
|
||||
handle_* p_;
|
||||
};
|
||||
|
||||
} // namespace shm
|
||||
} // namespace ipc
|
||||
179
include/libipc/spin_lock.h
Normal file
179
include/libipc/spin_lock.h
Normal file
@ -0,0 +1,179 @@
|
||||
/**
|
||||
* @file spin_lock.h
|
||||
* @author mutouyun (orz@orzz.org)
|
||||
* @brief Define spin locks
|
||||
* @date 2022-02-27
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "libipc/detect_plat.h"
|
||||
#include "libipc/def.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Gives hint to processor that improves performance of spin-wait loops.
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma push_macro("LIBIPC_LOCK_PAUSE_")
|
||||
#undef LIBIPC_LOCK_PAUSE_
|
||||
|
||||
#if defined(LIBIPC_CC_MSVC)
|
||||
# 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 LIBIPC_LOCK_PAUSE_() YieldProcessor()
|
||||
#elif defined(LIBIPC_CC_GNUC)
|
||||
# if defined(LIBIPC_INSTR_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 LIBIPC_LOCK_PAUSE_() __asm__ __volatile__("pause")
|
||||
# elif defined(LIBIPC_INSTR_I64)
|
||||
/*
|
||||
* @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 LIBIPC_LOCK_PAUSE_() __asm__ __volatile__ ("hint @pause")
|
||||
#elif defined(LIBIPC_INSTR_ARM)
|
||||
/*
|
||||
* @see ARM Architecture Reference Manuals (YIELD)
|
||||
* http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.subset.architecture.reference/index.html
|
||||
*/
|
||||
# define LIBIPC_LOCK_PAUSE_() __asm__ __volatile__ ("yield")
|
||||
# endif
|
||||
#endif /*compilers*/
|
||||
|
||||
#if !defined(LIBIPC_LOCK_PAUSE_)
|
||||
/*
|
||||
* Just use a compiler fence, prevent compiler from optimizing loop
|
||||
*/
|
||||
# define LIBIPC_LOCK_PAUSE_() std::atomic_signal_fence(std::memory_order_seq_cst)
|
||||
#endif /*!defined(LIBIPC_LOCK_PAUSE_)*/
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Yield to other threads
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
LIBIPC_NAMESPACE_BEG_
|
||||
|
||||
template <typename K>
|
||||
inline void yield(K &k) noexcept {
|
||||
if (k < 4) { /* Do nothing */ }
|
||||
else
|
||||
if (k < 16) { LIBIPC_LOCK_PAUSE_(); }
|
||||
else
|
||||
if (k < 32) { std::this_thread::yield(); }
|
||||
else {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
return;
|
||||
}
|
||||
++k;
|
||||
}
|
||||
|
||||
template <std::size_t N = 32, typename K, typename F>
|
||||
inline void sleep(K &k, F &&f) {
|
||||
if (k < static_cast<K>(N)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
else {
|
||||
static_cast<void>(std::forward<F>(f)());
|
||||
return;
|
||||
}
|
||||
++k;
|
||||
}
|
||||
|
||||
template <std::size_t N = 32, typename K>
|
||||
inline void sleep(K &k) {
|
||||
sleep<N>(k, [] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
#pragma pop_macro("LIBIPC_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);
|
||||
}
|
||||
};
|
||||
|
||||
LIBIPC_NAMESPACE_END_
|
||||
BIN
performance.xlsx
BIN
performance.xlsx
Binary file not shown.
@ -1,17 +1,9 @@
|
||||
project(ipc)
|
||||
|
||||
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc SRC_FILES)
|
||||
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/sync SRC_FILES)
|
||||
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/platform SRC_FILES)
|
||||
|
||||
file(GLOB HEAD_FILES
|
||||
${LIBIPC_PROJECT_DIR}/include/libipc/*.h
|
||||
${LIBIPC_PROJECT_DIR}/src/libipc/*.h
|
||||
${LIBIPC_PROJECT_DIR}/src/libipc/*.inc
|
||||
${LIBIPC_PROJECT_DIR}/src/libipc/circ/*.h
|
||||
${LIBIPC_PROJECT_DIR}/src/libipc/memory/*.h
|
||||
${LIBIPC_PROJECT_DIR}/src/libipc/platform/*.h
|
||||
${LIBIPC_PROJECT_DIR}/src/libipc/utility/*.h)
|
||||
${LIBIPC_PROJECT_DIR}/include/libipc/*.h)
|
||||
|
||||
if (LIBIPC_BUILD_SHARED_LIBS)
|
||||
add_library(${PROJECT_NAME} SHARED ${SRC_FILES} ${HEAD_FILES})
|
||||
@ -34,13 +26,12 @@ set_target_properties(${PROJECT_NAME}
|
||||
# set version
|
||||
set_target_properties(${PROJECT_NAME}
|
||||
PROPERTIES
|
||||
VERSION 1.2.0
|
||||
SOVERSION 3)
|
||||
VERSION 2.0.0
|
||||
SOVERSION 4)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PUBLIC ${LIBIPC_PROJECT_DIR}/include
|
||||
PRIVATE ${LIBIPC_PROJECT_DIR}/src
|
||||
$<$<BOOL:UNIX>:${LIBIPC_PROJECT_DIR}/src/libipc/platform/linux>)
|
||||
PRIVATE ${LIBIPC_PROJECT_DIR}/src)
|
||||
|
||||
if(NOT MSVC)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC
|
||||
|
||||
@ -1,87 +0,0 @@
|
||||
#include "libipc/buffer.h"
|
||||
#include "libipc/utility/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);
|
||||
}
|
||||
|
||||
bool operator!=(buffer const & b1, buffer const & b2) {
|
||||
return !(b1 == b2);
|
||||
}
|
||||
|
||||
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,136 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic> // std::atomic<?>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/rw_lock.h"
|
||||
|
||||
#include "libipc/circ/elem_def.h"
|
||||
#include "libipc/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<Policy> {
|
||||
public:
|
||||
using base_t = ipc::circ::conn_head<Policy>;
|
||||
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] {};
|
||||
|
||||
/**
|
||||
* @remarks 'warning C4348: redefinition of default parameter' with MSVC.
|
||||
* @see
|
||||
* - https://stackoverflow.com/questions/12656239/redefinition-of-default-template-parameter
|
||||
* - https://developercommunity.visualstudio.com/content/problem/425978/incorrect-c4348-warning-in-nested-template-declara.html
|
||||
*/
|
||||
template <typename P, bool/* = relat_trait<P>::is_multi_producer*/>
|
||||
struct sender_checker;
|
||||
|
||||
template <typename P>
|
||||
struct sender_checker<P, true> {
|
||||
constexpr static bool connect() noexcept {
|
||||
// always return true
|
||||
return true;
|
||||
}
|
||||
constexpr static void disconnect() noexcept {}
|
||||
};
|
||||
|
||||
template <typename P>
|
||||
struct sender_checker<P, false> {
|
||||
bool connect() noexcept {
|
||||
return !flag_.test_and_set(std::memory_order_acq_rel);
|
||||
}
|
||||
void disconnect() noexcept {
|
||||
flag_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
// in shm, it should be 0 whether it's initialized or not.
|
||||
std::atomic_flag flag_ = ATOMIC_FLAG_INIT;
|
||||
};
|
||||
|
||||
template <typename P, bool/* = relat_trait<P>::is_multi_consumer*/>
|
||||
struct receiver_checker;
|
||||
|
||||
template <typename P>
|
||||
struct receiver_checker<P, true> {
|
||||
constexpr static cc_t connect(base_t &conn) noexcept {
|
||||
return conn.connect();
|
||||
}
|
||||
constexpr static cc_t disconnect(base_t &conn, cc_t cc_id) noexcept {
|
||||
return conn.disconnect(cc_id);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename P>
|
||||
struct receiver_checker<P, false> : protected sender_checker<P, false> {
|
||||
cc_t connect(base_t &conn) noexcept {
|
||||
return sender_checker<P, false>::connect() ? conn.connect() : 0;
|
||||
}
|
||||
cc_t disconnect(base_t &conn, cc_t cc_id) noexcept {
|
||||
sender_checker<P, false>::disconnect();
|
||||
return conn.disconnect(cc_id);
|
||||
}
|
||||
};
|
||||
|
||||
sender_checker <policy_t, relat_trait<policy_t>::is_multi_producer> s_ckr_;
|
||||
receiver_checker<policy_t, relat_trait<policy_t>::is_multi_consumer> r_ckr_;
|
||||
|
||||
// make these be private
|
||||
using base_t::connect;
|
||||
using base_t::disconnect;
|
||||
|
||||
public:
|
||||
bool connect_sender() noexcept {
|
||||
return s_ckr_.connect();
|
||||
}
|
||||
|
||||
void disconnect_sender() noexcept {
|
||||
return s_ckr_.disconnect();
|
||||
}
|
||||
|
||||
cc_t connect_receiver() noexcept {
|
||||
return r_ckr_.connect(*this);
|
||||
}
|
||||
|
||||
cc_t disconnect_receiver(cc_t cc_id) noexcept {
|
||||
return r_ckr_.disconnect(*this, cc_id);
|
||||
}
|
||||
|
||||
cursor_t cursor() const noexcept {
|
||||
return head_.cursor();
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool push(F&& f) {
|
||||
return head_.push(std::forward<F>(f), block_);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool pop(cursor_t* cur, F&& f) {
|
||||
if (cur == nullptr) return false;
|
||||
return head_.pop(*cur, std::forward<F>(f), block_);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace circ
|
||||
} // namespace ipc
|
||||
@ -1,77 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <new>
|
||||
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/rw_lock.h"
|
||||
|
||||
#include "libipc/platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace circ {
|
||||
|
||||
using u1_t = ipc::uint_t<8>;
|
||||
using u2_t = ipc::uint_t<32>;
|
||||
|
||||
/** only supports max 32 connections in broadcast mode */
|
||||
using cc_t = u2_t;
|
||||
|
||||
constexpr u1_t index_of(u2_t c) noexcept {
|
||||
return static_cast<u1_t>(c);
|
||||
}
|
||||
|
||||
class conn_head_base {
|
||||
protected:
|
||||
std::atomic<cc_t> cc_{0}; // connections
|
||||
ipc::spin_lock lc_;
|
||||
std::atomic<bool> constructed_{false};
|
||||
|
||||
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_base;
|
||||
constructed_.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
conn_head_base() = default;
|
||||
conn_head_base(conn_head_base const &) = delete;
|
||||
conn_head_base &operator=(conn_head_base const &) = delete;
|
||||
|
||||
cc_t connections(std::memory_order order = std::memory_order_acquire) const noexcept {
|
||||
return this->cc_.load(order);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename P>
|
||||
class conn_head : public conn_head_base {
|
||||
public:
|
||||
cc_t connect() noexcept {
|
||||
return this->cc_.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
}
|
||||
|
||||
cc_t disconnect(cc_t cc_id) noexcept {
|
||||
if (cc_id == ~static_cast<circ::cc_t>(0u)) {
|
||||
// clear all connections
|
||||
this->cc_.store(0, std::memory_order_relaxed);
|
||||
return 0u;
|
||||
}
|
||||
else {
|
||||
return this->cc_.fetch_sub(1, std::memory_order_relaxed) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t conn_count(std::memory_order order = std::memory_order_acquire) const noexcept {
|
||||
return this->connections(order);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace circ
|
||||
} // namespace ipc
|
||||
@ -1,669 +1,2 @@
|
||||
|
||||
#include <type_traits>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <utility> // std::pair, std::move, std::forward
|
||||
#include <atomic>
|
||||
#include <type_traits> // aligned_storage_t
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
|
||||
#include "libipc/ipc.h"
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/shm.h"
|
||||
#include "libipc/pool_alloc.h"
|
||||
#include "libipc/queue.h"
|
||||
#include "libipc/policy.h"
|
||||
#include "libipc/rw_lock.h"
|
||||
#include "libipc/waiter.h"
|
||||
|
||||
#include "libipc/utility/log.h"
|
||||
#include "libipc/utility/id_pool.h"
|
||||
#include "libipc/utility/scope_guard.h"
|
||||
#include "libipc/utility/utility.h"
|
||||
|
||||
#include "libipc/memory/resource.h"
|
||||
#include "libipc/platform/detail.h"
|
||||
#include "libipc/circ/elem_array.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using msg_id_t = std::uint32_t;
|
||||
using acc_t = std::atomic<msg_id_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 cc_id_;
|
||||
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 cc_id, msg_id_t id, std::int32_t remain, void const * data, std::size_t size)
|
||||
: msg_t<0, AlignSize> {cc_id, id, remain, (data == nullptr) || (size == 0)} {
|
||||
if (this->storage_) {
|
||||
if (data != nullptr) {
|
||||
// copy storage-id
|
||||
*reinterpret_cast<ipc::storage_id_t*>(&data_) =
|
||||
*static_cast<ipc::storage_id_t const *>(data);
|
||||
}
|
||||
}
|
||||
else std::memcpy(&data_, data, size);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
ipc::buff_t make_cache(T& data, std::size_t size) {
|
||||
auto ptr = ipc::mem::alloc(size);
|
||||
std::memcpy(ptr, &data, (ipc::detail::min)(sizeof(data), size));
|
||||
return { ptr, size, ipc::mem::free };
|
||||
}
|
||||
|
||||
struct cache_t {
|
||||
std::size_t fill_;
|
||||
ipc::buff_t buff_;
|
||||
|
||||
cache_t(std::size_t f, ipc::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<ipc::byte_t*>(buff_.data()) + fill_, data, new_fill - fill_);
|
||||
fill_ = new_fill;
|
||||
}
|
||||
};
|
||||
|
||||
auto cc_acc() {
|
||||
static ipc::shm::handle acc_h("__CA_CONN__", sizeof(acc_t));
|
||||
return static_cast<acc_t*>(acc_h.get());
|
||||
}
|
||||
|
||||
IPC_CONSTEXPR_ std::size_t align_chunk_size(std::size_t size) noexcept {
|
||||
return (((size - 1) / ipc::large_msg_align) + 1) * ipc::large_msg_align;
|
||||
}
|
||||
|
||||
IPC_CONSTEXPR_ std::size_t calc_chunk_size(std::size_t size) noexcept {
|
||||
return ipc::make_align(alignof(std::max_align_t), align_chunk_size(
|
||||
ipc::make_align(alignof(std::max_align_t), sizeof(std::atomic<ipc::circ::cc_t>)) + size));
|
||||
}
|
||||
|
||||
struct chunk_t {
|
||||
std::atomic<ipc::circ::cc_t> &conns() noexcept {
|
||||
return *reinterpret_cast<std::atomic<ipc::circ::cc_t> *>(this);
|
||||
}
|
||||
|
||||
void *data() noexcept {
|
||||
return reinterpret_cast<ipc::byte_t *>(this)
|
||||
+ ipc::make_align(alignof(std::max_align_t), sizeof(std::atomic<ipc::circ::cc_t>));
|
||||
}
|
||||
};
|
||||
|
||||
struct chunk_info_t {
|
||||
ipc::id_pool<> pool_;
|
||||
ipc::spin_lock lock_;
|
||||
|
||||
IPC_CONSTEXPR_ static std::size_t chunks_mem_size(std::size_t chunk_size) noexcept {
|
||||
return ipc::id_pool<>::max_count * chunk_size;
|
||||
}
|
||||
|
||||
ipc::byte_t *chunks_mem() noexcept {
|
||||
return reinterpret_cast<ipc::byte_t *>(this + 1);
|
||||
}
|
||||
|
||||
chunk_t *at(std::size_t chunk_size, ipc::storage_id_t id) noexcept {
|
||||
if (id < 0) return nullptr;
|
||||
return reinterpret_cast<chunk_t *>(chunks_mem() + (chunk_size * id));
|
||||
}
|
||||
};
|
||||
|
||||
auto& chunk_storages() {
|
||||
class chunk_handle_t {
|
||||
ipc::shm::handle handle_;
|
||||
|
||||
public:
|
||||
chunk_info_t *get_info(std::size_t chunk_size) {
|
||||
if (!handle_.valid() &&
|
||||
!handle_.acquire( ("__CHUNK_INFO__" + ipc::to_string(chunk_size)).c_str(),
|
||||
sizeof(chunk_info_t) + chunk_info_t::chunks_mem_size(chunk_size) )) {
|
||||
ipc::error("[chunk_storages] chunk_shm.id_info_.acquire failed: chunk_size = %zd\n", chunk_size);
|
||||
return nullptr;
|
||||
}
|
||||
auto info = static_cast<chunk_info_t*>(handle_.get());
|
||||
if (info == nullptr) {
|
||||
ipc::error("[chunk_storages] chunk_shm.id_info_.get failed: chunk_size = %zd\n", chunk_size);
|
||||
return nullptr;
|
||||
}
|
||||
return info;
|
||||
}
|
||||
};
|
||||
static ipc::map<std::size_t, chunk_handle_t> chunk_hs;
|
||||
return chunk_hs;
|
||||
}
|
||||
|
||||
chunk_info_t *chunk_storage_info(std::size_t chunk_size) {
|
||||
auto &storages = chunk_storages();
|
||||
std::decay_t<decltype(storages)>::iterator it;
|
||||
{
|
||||
static ipc::rw_lock lock;
|
||||
IPC_UNUSED_ std::shared_lock<ipc::rw_lock> guard {lock};
|
||||
if ((it = storages.find(chunk_size)) == storages.end()) {
|
||||
using chunk_handle_t = std::decay_t<decltype(storages)>::value_type::second_type;
|
||||
guard.unlock();
|
||||
IPC_UNUSED_ std::lock_guard<ipc::rw_lock> guard {lock};
|
||||
it = storages.emplace(chunk_size, chunk_handle_t{}).first;
|
||||
}
|
||||
}
|
||||
return it->second.get_info(chunk_size);
|
||||
}
|
||||
|
||||
std::pair<ipc::storage_id_t, void*> acquire_storage(std::size_t size, ipc::circ::cc_t conns) {
|
||||
std::size_t chunk_size = calc_chunk_size(size);
|
||||
auto info = chunk_storage_info(chunk_size);
|
||||
if (info == nullptr) return {};
|
||||
|
||||
info->lock_.lock();
|
||||
info->pool_.prepare();
|
||||
// got an unique id
|
||||
auto id = info->pool_.acquire();
|
||||
info->lock_.unlock();
|
||||
|
||||
auto chunk = info->at(chunk_size, id);
|
||||
if (chunk == nullptr) return {};
|
||||
chunk->conns().store(conns, std::memory_order_relaxed);
|
||||
return { id, chunk->data() };
|
||||
}
|
||||
|
||||
void *find_storage(ipc::storage_id_t id, std::size_t size) {
|
||||
if (id < 0) {
|
||||
ipc::error("[find_storage] id is invalid: id = %ld, size = %zd\n", (long)id, size);
|
||||
return nullptr;
|
||||
}
|
||||
std::size_t chunk_size = calc_chunk_size(size);
|
||||
auto info = chunk_storage_info(chunk_size);
|
||||
if (info == nullptr) return nullptr;
|
||||
return info->at(chunk_size, id)->data();
|
||||
}
|
||||
|
||||
void release_storage(ipc::storage_id_t id, std::size_t size) {
|
||||
if (id < 0) {
|
||||
ipc::error("[release_storage] id is invalid: id = %ld, size = %zd\n", (long)id, size);
|
||||
return;
|
||||
}
|
||||
std::size_t chunk_size = calc_chunk_size(size);
|
||||
auto info = chunk_storage_info(chunk_size);
|
||||
if (info == nullptr) return;
|
||||
info->lock_.lock();
|
||||
info->pool_.release(id);
|
||||
info->lock_.unlock();
|
||||
}
|
||||
|
||||
template <ipc::relat Rp, ipc::relat Rc>
|
||||
bool sub_rc(ipc::wr<Rp, Rc, ipc::trans::unicast>,
|
||||
std::atomic<ipc::circ::cc_t> &/*conns*/, ipc::circ::cc_t /*curr_conns*/, ipc::circ::cc_t /*conn_id*/) noexcept {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <ipc::relat Rp, ipc::relat Rc>
|
||||
bool sub_rc(ipc::wr<Rp, Rc, ipc::trans::broadcast>,
|
||||
std::atomic<ipc::circ::cc_t> &conns, ipc::circ::cc_t curr_conns, ipc::circ::cc_t conn_id) noexcept {
|
||||
auto last_conns = curr_conns & ~conn_id;
|
||||
for (unsigned k = 0;;) {
|
||||
auto chunk_conns = conns.load(std::memory_order_acquire);
|
||||
if (conns.compare_exchange_weak(chunk_conns, chunk_conns & last_conns, std::memory_order_release)) {
|
||||
return (chunk_conns & last_conns) == 0;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Flag>
|
||||
void recycle_storage(ipc::storage_id_t id, std::size_t size, ipc::circ::cc_t curr_conns, ipc::circ::cc_t conn_id) {
|
||||
if (id < 0) {
|
||||
ipc::error("[recycle_storage] id is invalid: id = %ld, size = %zd\n", (long)id, size);
|
||||
return;
|
||||
}
|
||||
std::size_t chunk_size = calc_chunk_size(size);
|
||||
auto info = chunk_storage_info(chunk_size);
|
||||
if (info == nullptr) return;
|
||||
|
||||
auto chunk = info->at(chunk_size, id);
|
||||
if (chunk == nullptr) return;
|
||||
|
||||
if (!sub_rc(Flag{}, chunk->conns(), curr_conns, conn_id)) {
|
||||
return;
|
||||
}
|
||||
info->lock_.lock();
|
||||
info->pool_.release(id);
|
||||
info->lock_.unlock();
|
||||
}
|
||||
|
||||
template <typename MsgT>
|
||||
bool clear_message(void* p) {
|
||||
auto msg = static_cast<MsgT*>(p);
|
||||
if (msg->storage_) {
|
||||
std::int32_t r_size = static_cast<std::int32_t>(ipc::data_length) + msg->remain_;
|
||||
if (r_size <= 0) {
|
||||
ipc::error("[clear_message] invalid msg size: %d\n", (int)r_size);
|
||||
return true;
|
||||
}
|
||||
release_storage(
|
||||
*reinterpret_cast<ipc::storage_id_t*>(&msg->data_),
|
||||
static_cast<std::size_t>(r_size));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct conn_info_head {
|
||||
|
||||
ipc::string name_;
|
||||
msg_id_t cc_id_; // connection-info id
|
||||
ipc::detail::waiter cc_waiter_, wt_waiter_, rd_waiter_;
|
||||
ipc::shm::handle acc_h_;
|
||||
|
||||
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)} {
|
||||
}
|
||||
|
||||
void quit_waiting() {
|
||||
cc_waiter_.quit_waiting();
|
||||
wt_waiter_.quit_waiting();
|
||||
rd_waiter_.quit_waiting();
|
||||
}
|
||||
|
||||
auto acc() {
|
||||
return static_cast<acc_t*>(acc_h_.get());
|
||||
}
|
||||
|
||||
auto& recv_cache() {
|
||||
thread_local ipc::unordered_map<msg_id_t, cache_t> tls;
|
||||
return tls;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename W, typename F>
|
||||
bool wait_for(W& waiter, F&& pred, std::uint64_t tm) {
|
||||
if (tm == 0) return !pred();
|
||||
for (unsigned k = 0; pred();) {
|
||||
bool ret = true;
|
||||
ipc::sleep(k, [&k, &ret, &waiter, &pred, tm] {
|
||||
ret = waiter.wait_if(std::forward<F>(pred), tm);
|
||||
k = 0;
|
||||
});
|
||||
if (!ret) return false; // timeout or fail
|
||||
if (k == 0) break; // k has been reset
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Policy,
|
||||
std::size_t DataSize = ipc::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()} {
|
||||
}
|
||||
|
||||
void disconnect_receiver() {
|
||||
bool dis = que_.disconnect();
|
||||
this->quit_waiting();
|
||||
if (dis) {
|
||||
this->recv_cache().clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
template <typename Policy>
|
||||
struct detail_impl {
|
||||
|
||||
using policy_t = Policy;
|
||||
using flag_t = typename policy_t::flag_t;
|
||||
using queue_t = typename queue_generator<policy_t>::queue_t;
|
||||
using conn_info_t = typename queue_generator<policy_t>::conn_info_t;
|
||||
|
||||
constexpr static conn_info_t* info_of(ipc::handle_t h) noexcept {
|
||||
return static_cast<conn_info_t*>(h);
|
||||
}
|
||||
|
||||
constexpr static queue_t* queue_of(ipc::handle_t h) noexcept {
|
||||
return (info_of(h) == nullptr) ? nullptr : &(info_of(h)->que_);
|
||||
}
|
||||
|
||||
/* API implementations */
|
||||
|
||||
static void disconnect(ipc::handle_t h) {
|
||||
auto que = queue_of(h);
|
||||
if (que == nullptr) {
|
||||
return;
|
||||
}
|
||||
que->shut_sending();
|
||||
assert(info_of(h) != nullptr);
|
||||
info_of(h)->disconnect_receiver();
|
||||
}
|
||||
|
||||
static bool reconnect(ipc::handle_t * ph, bool start_to_recv) {
|
||||
assert(ph != nullptr);
|
||||
assert(*ph != nullptr);
|
||||
auto que = queue_of(*ph);
|
||||
if (que == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (start_to_recv) {
|
||||
que->shut_sending();
|
||||
if (que->connect()) { // wouldn't connect twice
|
||||
info_of(*ph)->cc_waiter_.broadcast();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// start_to_recv == false
|
||||
if (que->connected()) {
|
||||
info_of(*ph)->disconnect_receiver();
|
||||
}
|
||||
return que->ready_sending();
|
||||
}
|
||||
|
||||
static bool connect(ipc::handle_t * ph, char const * name, bool start_to_recv) {
|
||||
assert(ph != nullptr);
|
||||
if (*ph == nullptr) {
|
||||
*ph = ipc::mem::alloc<conn_info_t>(name);
|
||||
}
|
||||
return reconnect(ph, start_to_recv);
|
||||
}
|
||||
|
||||
static void destroy(ipc::handle_t h) {
|
||||
disconnect(h);
|
||||
ipc::mem::free(info_of(h));
|
||||
}
|
||||
|
||||
static std::size_t recv_count(ipc::handle_t h) noexcept {
|
||||
auto que = queue_of(h);
|
||||
if (que == nullptr) {
|
||||
return ipc::invalid_value;
|
||||
}
|
||||
return que->conn_count();
|
||||
}
|
||||
|
||||
static bool wait_for_recv(ipc::handle_t h, std::size_t r_count, std::uint64_t tm) {
|
||||
auto que = queue_of(h);
|
||||
if (que == nullptr) {
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (que->elems() == nullptr) {
|
||||
ipc::error("fail: send, queue_of(h)->elems() == nullptr\n");
|
||||
return false;
|
||||
}
|
||||
if (!que->ready_sending()) {
|
||||
ipc::error("fail: send, que->ready_sending() == false\n");
|
||||
return false;
|
||||
}
|
||||
ipc::circ::cc_t conns = que->elems()->connections(std::memory_order_relaxed);
|
||||
if (conns == 0) {
|
||||
ipc::error("fail: send, there is no receiver on this connection.\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 > ipc::large_msg_limit) {
|
||||
auto dat = acquire_storage(size, conns);
|
||||
void * buf = dat.second;
|
||||
if (buf != nullptr) {
|
||||
std::memcpy(buf, data, size);
|
||||
return try_push(static_cast<std::int32_t>(size) -
|
||||
static_cast<std::int32_t>(ipc::data_length), &(dat.first), 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
|
||||
std::int32_t offset = 0;
|
||||
for (std::int32_t i = 0; i < static_cast<std::int32_t>(size / ipc::data_length); ++i, offset += ipc::data_length) {
|
||||
if (!try_push(static_cast<std::int32_t>(size) - offset - static_cast<std::int32_t>(ipc::data_length),
|
||||
static_cast<ipc::byte_t const *>(data) + offset, ipc::data_length)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// if remain > 0, this is the last message fragment
|
||||
std::int32_t remain = static_cast<std::int32_t>(size) - offset;
|
||||
if (remain > 0) {
|
||||
if (!try_push(remain - static_cast<std::int32_t>(ipc::data_length),
|
||||
static_cast<ipc::byte_t const *>(data) + offset,
|
||||
static_cast<std::size_t>(remain))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool send(ipc::handle_t h, void const * data, std::size_t size, std::uint64_t tm) {
|
||||
return send([tm](auto info, auto que, auto msg_id) {
|
||||
return [tm, info, que, msg_id](std::int32_t remain, void const * data, std::size_t size) {
|
||||
if (!wait_for(info->wt_waiter_, [&] {
|
||||
return !que->push(
|
||||
[](void*) { return true; },
|
||||
info->cc_id_, msg_id, remain, data, size);
|
||||
}, tm)) {
|
||||
return false;
|
||||
}
|
||||
info->rd_waiter_.broadcast();
|
||||
return true;
|
||||
};
|
||||
}, h, data, size);
|
||||
}
|
||||
|
||||
static ipc::buff_t recv(ipc::handle_t h, std::uint64_t tm) {
|
||||
auto que = queue_of(h);
|
||||
if (que == nullptr) {
|
||||
ipc::error("fail: recv, queue_of(h) == nullptr\n");
|
||||
return {};
|
||||
}
|
||||
if (!que->connected()) {
|
||||
// hasn't connected yet, just return.
|
||||
return {};
|
||||
}
|
||||
auto& rc = info_of(h)->recv_cache();
|
||||
for (;;) {
|
||||
// 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)) {
|
||||
// pop failed, just return.
|
||||
return {};
|
||||
}
|
||||
info_of(h)->wt_waiter_.broadcast();
|
||||
if ((info_of(h)->acc() != nullptr) && (msg.cc_id_ == info_of(h)->cc_id_)) {
|
||||
continue; // ignore message to self
|
||||
}
|
||||
// msg.remain_ may minus & abs(msg.remain_) < data_length
|
||||
std::int32_t r_size = static_cast<std::int32_t>(ipc::data_length) + msg.remain_;
|
||||
if (r_size <= 0) {
|
||||
ipc::error("fail: recv, r_size = %d\n", (int)r_size);
|
||||
return {};
|
||||
}
|
||||
std::size_t msg_size = static_cast<std::size_t>(r_size);
|
||||
// large message
|
||||
if (msg.storage_) {
|
||||
ipc::storage_id_t buf_id = *reinterpret_cast<ipc::storage_id_t*>(&msg.data_);
|
||||
void* buf = find_storage(buf_id, msg_size);
|
||||
if (buf != nullptr) {
|
||||
struct recycle_t {
|
||||
ipc::storage_id_t storage_id;
|
||||
ipc::circ::cc_t curr_conns;
|
||||
ipc::circ::cc_t conn_id;
|
||||
} *r_info = ipc::mem::alloc<recycle_t>(recycle_t{
|
||||
buf_id, que->elems()->connections(std::memory_order_relaxed), que->connected_id()
|
||||
});
|
||||
if (r_info == nullptr) {
|
||||
ipc::log("fail: ipc::mem::alloc<recycle_t>.\n");
|
||||
return ipc::buff_t{buf, msg_size}; // no recycle
|
||||
} else {
|
||||
return ipc::buff_t{buf, msg_size, [](void* p_info, std::size_t size) {
|
||||
auto r_info = static_cast<recycle_t *>(p_info);
|
||||
IPC_UNUSED_ auto finally = ipc::guard([r_info] {
|
||||
ipc::mem::free(r_info);
|
||||
});
|
||||
recycle_storage<flag_t>(r_info->storage_id, size, r_info->curr_conns, r_info->conn_id);
|
||||
}, r_info};
|
||||
}
|
||||
} else {
|
||||
ipc::log("fail: shm::handle for large message. msg_id: %zd, buf_id: %zd, size: %zd\n", msg.id_, buf_id, msg_size);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// find cache with msg.id_
|
||||
auto cac_it = rc.find(msg.id_);
|
||||
if (cac_it == rc.end()) {
|
||||
if (msg_size <= ipc::data_length) {
|
||||
return make_cache(msg.data_, msg_size);
|
||||
}
|
||||
// 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 { ipc::data_length, make_cache(msg.data_, msg_size) });
|
||||
}
|
||||
// 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_), msg_size);
|
||||
// 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_), ipc::data_length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ipc::buff_t try_recv(ipc::handle_t h) {
|
||||
return recv(h, 0);
|
||||
}
|
||||
|
||||
}; // detail_impl<Policy>
|
||||
|
||||
template <typename Flag>
|
||||
using policy_t = ipc::policy::choose<ipc::circ::elem_array, Flag>;
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
namespace ipc {
|
||||
|
||||
template <typename Flag>
|
||||
bool chan_impl<Flag>::connect(ipc::handle_t * ph, char const * name, unsigned mode) {
|
||||
return detail_impl<policy_t<Flag>>::connect(ph, name, mode & receiver);
|
||||
}
|
||||
|
||||
template <typename Flag>
|
||||
bool chan_impl<Flag>::reconnect(ipc::handle_t * ph, unsigned mode) {
|
||||
return detail_impl<policy_t<Flag>>::reconnect(ph, mode & receiver);
|
||||
}
|
||||
|
||||
template <typename Flag>
|
||||
void chan_impl<Flag>::disconnect(ipc::handle_t h) {
|
||||
detail_impl<policy_t<Flag>>::disconnect(h);
|
||||
}
|
||||
|
||||
template <typename Flag>
|
||||
void chan_impl<Flag>::destroy(ipc::handle_t h) {
|
||||
detail_impl<policy_t<Flag>>::destroy(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::uint64_t tm) {
|
||||
return detail_impl<policy_t<Flag>>::wait_for_recv(h, r_count, tm);
|
||||
}
|
||||
|
||||
template <typename Flag>
|
||||
bool chan_impl<Flag>::send(ipc::handle_t h, void const * data, std::size_t size, std::uint64_t tm) {
|
||||
return detail_impl<policy_t<Flag>>::send(h, data, size, tm);
|
||||
}
|
||||
|
||||
template <typename Flag>
|
||||
buff_t chan_impl<Flag>::recv(ipc::handle_t h, std::uint64_t tm) {
|
||||
return detail_impl<policy_t<Flag>>::recv(h, tm);
|
||||
}
|
||||
|
||||
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 >>; // TBD
|
||||
// template struct chan_impl<ipc::wr<relat::multi , relat::multi , trans::unicast >>; // TBD
|
||||
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
|
||||
|
||||
@ -1,424 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <iterator>
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <cstdlib>
|
||||
#include <cassert> // assert
|
||||
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/rw_lock.h"
|
||||
|
||||
#include "libipc/utility/concept.h"
|
||||
#include "libipc/memory/allocator_wrapper.h"
|
||||
#include "libipc/platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
class static_alloc {
|
||||
public:
|
||||
static void swap(static_alloc&) {}
|
||||
|
||||
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(std::move(std::declval<Type>())));
|
||||
|
||||
class scope_alloc_base {
|
||||
protected:
|
||||
struct block_t {
|
||||
std::size_t size_;
|
||||
block_t * next_;
|
||||
} * 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(); }
|
||||
|
||||
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* alloc(std::size_t size) {
|
||||
std::size_t real_size = aligned_block_size + size;
|
||||
auto curr = static_cast<block_t*>(alloc_.alloc(real_size));
|
||||
curr->size_ = real_size;
|
||||
curr->next_ = head_;
|
||||
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 block_size_;
|
||||
std::size_t init_expand_;
|
||||
void * cursor_;
|
||||
|
||||
void init(std::size_t block_size, std::size_t init_expand) {
|
||||
block_size_ = block_size;
|
||||
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:
|
||||
bool operator<(fixed_alloc_base const & right) const {
|
||||
return init_expand_ < right.init_expand_;
|
||||
}
|
||||
|
||||
void set_block_size(std::size_t block_size) {
|
||||
block_size_ = block_size;
|
||||
}
|
||||
|
||||
void swap(fixed_alloc_base& rhs) {
|
||||
std::swap(block_size_ , rhs.block_size_);
|
||||
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) {
|
||||
assert(block_size_ == rhs.block_size_);
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename AllocP, typename ExpandP>
|
||||
class fixed_alloc : public detail::fixed_alloc_base {
|
||||
public:
|
||||
using base_t = detail::fixed_alloc_base;
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
private:
|
||||
alloc_policy alloc_;
|
||||
|
||||
void* try_expand() {
|
||||
if (empty()) {
|
||||
auto size = ExpandP::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 block_size, std::size_t init_expand = 1) {
|
||||
init(block_size, init_expand);
|
||||
}
|
||||
|
||||
fixed_alloc(fixed_alloc && rhs) {
|
||||
init(0, 0);
|
||||
swap(rhs);
|
||||
}
|
||||
|
||||
fixed_alloc& operator=(fixed_alloc rhs) {
|
||||
swap(rhs);
|
||||
return (*this);
|
||||
}
|
||||
|
||||
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_));
|
||||
}
|
||||
|
||||
void* alloc() {
|
||||
void* p = try_expand();
|
||||
cursor_ = next(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
void* alloc(std::size_t) {
|
||||
return alloc();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <std::size_t BaseSize = sizeof(void*) * 1024,
|
||||
std::size_t LimitSize = (std::numeric_limits<std::uint32_t>::max)()>
|
||||
struct fixed_expand_policy {
|
||||
|
||||
enum : std::size_t {
|
||||
base_size = BaseSize,
|
||||
limit_size = LimitSize
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static std::size_t next(std::size_t block_size, std::size_t & e) {
|
||||
auto n = ipc::detail::max<std::size_t>(block_size, base_size) * e;
|
||||
e = ipc::detail::min<std::size_t>(limit_size, 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<AllocP, ExpandP> {
|
||||
public:
|
||||
using base_t = detail::fixed_alloc<AllocP, ExpandP>;
|
||||
|
||||
enum : std::size_t {
|
||||
block_size = ipc::detail::max<std::size_t>(BlockSize, sizeof(void*))
|
||||
};
|
||||
|
||||
public:
|
||||
explicit fixed_alloc(std::size_t init_expand)
|
||||
: base_t(block_size, init_expand) {
|
||||
}
|
||||
|
||||
fixed_alloc() : fixed_alloc(1) {}
|
||||
|
||||
fixed_alloc(fixed_alloc && rhs)
|
||||
: base_t(std::move(rhs)) {
|
||||
}
|
||||
|
||||
fixed_alloc& operator=(fixed_alloc rhs) {
|
||||
swap(rhs);
|
||||
return (*this);
|
||||
}
|
||||
|
||||
void swap(fixed_alloc& rhs) {
|
||||
base_t::swap(rhs);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Variable-size blocks allocation (without alignment)
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
class variable_alloc_base {
|
||||
protected:
|
||||
byte_t * head_ = nullptr, * tail_ = nullptr;
|
||||
|
||||
public:
|
||||
void swap(variable_alloc_base & rhs) {
|
||||
std::swap(head_, rhs.head_);
|
||||
std::swap(tail_, rhs.tail_);
|
||||
}
|
||||
|
||||
std::size_t remain() const noexcept {
|
||||
return static_cast<std::size_t>(tail_ - head_);
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return remain() == 0;
|
||||
}
|
||||
|
||||
void take(variable_alloc_base && rhs) {
|
||||
if (remain() < rhs.remain()) {
|
||||
// replace this by rhs
|
||||
head_ = rhs.head_;
|
||||
tail_ = rhs.tail_;
|
||||
}
|
||||
// discard rhs
|
||||
rhs.head_ = rhs.tail_ = 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 alloc_policy = AllocP;
|
||||
|
||||
enum : std::size_t {
|
||||
aligned_chunk_size = detail::aligned(ChunkSize, alignof(std::max_align_t))
|
||||
};
|
||||
|
||||
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); }
|
||||
|
||||
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_));
|
||||
}
|
||||
|
||||
void* alloc(std::size_t size) {
|
||||
/*
|
||||
* byte alignment is always alignof(std::max_align_t).
|
||||
*/
|
||||
size = detail::aligned(size, alignof(std::max_align_t));
|
||||
void* ptr;
|
||||
// size would never be 0 here
|
||||
if (remain() < size) {
|
||||
std::size_t chunk_size = ipc::detail::max<std::size_t>(aligned_chunk_size, size);
|
||||
ptr = alloc_.alloc(chunk_size);
|
||||
tail_ = static_cast<byte_t*>(ptr) + chunk_size;
|
||||
head_ = tail_ - (chunk_size - size);
|
||||
}
|
||||
else {
|
||||
ptr = head_;
|
||||
head_ += size;
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
@ -1,121 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <utility> // std::forward
|
||||
#include <cstddef>
|
||||
|
||||
#include "libipc/pool_alloc.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// The allocator wrapper class for STL
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T, typename AllocP>
|
||||
struct rebind {
|
||||
template <typename U>
|
||||
using alloc_t = AllocP;
|
||||
};
|
||||
|
||||
template <typename T, template <typename> class AllocT>
|
||||
struct rebind<T, AllocT<T>> {
|
||||
template <typename U>
|
||||
using alloc_t = AllocT<U>;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
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() noexcept {}
|
||||
|
||||
// construct by copying (do nothing)
|
||||
allocator_wrapper (const allocator_wrapper<T, AllocP>&) noexcept {}
|
||||
allocator_wrapper& operator=(const allocator_wrapper<T, AllocP>&) noexcept { return *this; }
|
||||
|
||||
// construct from a related allocator (do nothing)
|
||||
template <typename U, typename AllocU> allocator_wrapper (const allocator_wrapper<U, AllocU>&) noexcept {}
|
||||
template <typename U, typename AllocU> allocator_wrapper& operator=(const allocator_wrapper<U, AllocU>&) noexcept { return *this; }
|
||||
|
||||
allocator_wrapper (allocator_wrapper && rhs) noexcept : alloc_ ( std::move(rhs.alloc_) ) {}
|
||||
allocator_wrapper& operator=(allocator_wrapper && rhs) noexcept { alloc_ = std::move(rhs.alloc_); return *this; }
|
||||
|
||||
public:
|
||||
// the other type of std_allocator
|
||||
template <typename U>
|
||||
struct rebind {
|
||||
using other = allocator_wrapper< U, typename detail::rebind<T, AllocP>::template alloc_t<U> >;
|
||||
};
|
||||
|
||||
constexpr size_type max_size(void) const noexcept {
|
||||
return (std::numeric_limits<size_type>::max)() / sizeof(value_type);
|
||||
}
|
||||
|
||||
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(value_type)));
|
||||
}
|
||||
|
||||
void deallocate(pointer p, size_type count) noexcept {
|
||||
alloc_.free(p, count * sizeof(value_type));
|
||||
}
|
||||
|
||||
template <typename... P>
|
||||
static void construct(pointer p, P && ... params) {
|
||||
ipc::mem::construct(p, std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
static void destroy(pointer p) {
|
||||
ipc::mem::destruct(p);
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
@ -1,90 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
|
||||
#include "libipc/def.h"
|
||||
|
||||
#include "libipc/memory/alloc.h"
|
||||
#include "libipc/memory/wrapper.h"
|
||||
#include "libipc/platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
//using async_pool_alloc = static_wrapper<variable_wrapper<async_wrapper<
|
||||
// detail::fixed_alloc<
|
||||
// variable_alloc <sizeof(void*) * 1024 * 256>,
|
||||
// fixed_expand_policy<sizeof(void*) * 1024, sizeof(void*) * 1024 * 256>
|
||||
// >,
|
||||
// default_recycler >>>;
|
||||
using async_pool_alloc = ipc::mem::static_alloc;
|
||||
|
||||
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 T>
|
||||
struct hash : public std::hash<T> {};
|
||||
|
||||
template <typename Key, typename T>
|
||||
using unordered_map = std::unordered_map<
|
||||
Key, T, ipc::hash<Key>, std::equal_to<Key>, ipc::mem::allocator<std::pair<Key const, T>>
|
||||
>;
|
||||
|
||||
template <typename Key, typename T>
|
||||
using map = std::map<
|
||||
Key, T, std::less<Key>, ipc::mem::allocator<std::pair<Key const, 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 <> struct hash<string> {
|
||||
std::size_t operator()(string const &val) const noexcept {
|
||||
return std::hash<char const *>{}(val.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct hash<wstring> {
|
||||
std::size_t operator()(wstring const &val) const noexcept {
|
||||
return std::hash<wchar_t const *>{}(val.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
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,327 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <tuple>
|
||||
#include <thread>
|
||||
#include <deque> // std::deque
|
||||
#include <functional> // std::function
|
||||
#include <utility> // std::forward
|
||||
#include <cstddef>
|
||||
#include <cassert> // assert
|
||||
#include <type_traits> // std::aligned_storage_t
|
||||
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/rw_lock.h"
|
||||
#include "libipc/pool_alloc.h"
|
||||
|
||||
#include "libipc/utility/concept.h"
|
||||
#include "libipc/memory/alloc.h"
|
||||
#include "libipc/platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Thread-safe allocation wrapper
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
IPC_CONCEPT_(is_comparable, operator<(std::declval<Type>()));
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename AllocP, bool = detail::is_comparable<AllocP>::value>
|
||||
class limited_recycler;
|
||||
|
||||
template <typename AllocP>
|
||||
class limited_recycler<AllocP, true> {
|
||||
public:
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
protected:
|
||||
std::deque<alloc_policy> master_allocs_;
|
||||
ipc::spin_lock master_lock_;
|
||||
|
||||
template <typename F>
|
||||
void take_first_do(F && pred) {
|
||||
auto it = master_allocs_.begin();
|
||||
pred(const_cast<alloc_policy&>(*it));
|
||||
master_allocs_.erase(it);
|
||||
}
|
||||
|
||||
public:
|
||||
void try_recover(alloc_policy & alc) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(master_lock_);
|
||||
if (master_allocs_.empty()) return;
|
||||
take_first_do([&alc](alloc_policy & first) { alc.swap(first); });
|
||||
}
|
||||
|
||||
void collect(alloc_policy && alc) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(master_lock_);
|
||||
if (master_allocs_.size() >= 32) {
|
||||
take_first_do([](alloc_policy &) {}); // erase first
|
||||
}
|
||||
master_allocs_.emplace_back(std::move(alc));
|
||||
}
|
||||
|
||||
IPC_CONSTEXPR_ auto try_replenish(alloc_policy&, std::size_t) noexcept {}
|
||||
};
|
||||
|
||||
template <typename AllocP>
|
||||
class default_recycler : public limited_recycler<AllocP> {
|
||||
|
||||
IPC_CONCEPT_(has_remain, remain());
|
||||
IPC_CONCEPT_(has_empty , empty());
|
||||
|
||||
template <typename A>
|
||||
void try_fill(A & alc) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(this->master_lock_);
|
||||
if (this->master_allocs_.empty()) return;
|
||||
this->take_first_do([&alc](alloc_policy & first) { alc.take(std::move(first)); });
|
||||
}
|
||||
|
||||
public:
|
||||
using alloc_policy = typename limited_recycler<AllocP>::alloc_policy;
|
||||
|
||||
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;
|
||||
this->try_fill(alc);
|
||||
}
|
||||
|
||||
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;
|
||||
this->try_fill(alc);
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
auto try_replenish(alloc_policy & alc, std::size_t /*size*/)
|
||||
-> ipc::require<!detail::has_take<A>::value && has_empty<A>::value> {
|
||||
if (!alc.empty()) return;
|
||||
this->try_recover(alc);
|
||||
}
|
||||
|
||||
template <typename A = AllocP>
|
||||
IPC_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> {
|
||||
// Do Nothing.
|
||||
}
|
||||
};
|
||||
|
||||
template <typename AllocP>
|
||||
class empty_recycler {
|
||||
public:
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
IPC_CONSTEXPR_ void try_recover(alloc_policy&) noexcept {}
|
||||
IPC_CONSTEXPR_ auto try_replenish(alloc_policy&, std::size_t) noexcept {}
|
||||
IPC_CONSTEXPR_ void collect(alloc_policy&&) noexcept {}
|
||||
};
|
||||
|
||||
template <typename AllocP,
|
||||
template <typename> class RecyclerP = default_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) = default;
|
||||
|
||||
template <typename ... P>
|
||||
alloc_proxy(async_wrapper* w, P && ... pars)
|
||||
: AllocP(std::forward<P>(pars) ...), w_(w) {
|
||||
assert(w_ != nullptr);
|
||||
w_->recycler_.try_recover(*this);
|
||||
}
|
||||
|
||||
~alloc_proxy() {
|
||||
w_->recycler_.collect(std::move(*this));
|
||||
}
|
||||
|
||||
auto alloc(std::size_t size) {
|
||||
w_->recycler_.try_replenish(*this, size);
|
||||
return AllocP::alloc(size);
|
||||
}
|
||||
};
|
||||
|
||||
friend class alloc_proxy;
|
||||
using ref_t = alloc_proxy&;
|
||||
|
||||
std::function<ref_t()> get_alloc_;
|
||||
|
||||
public:
|
||||
template <typename ... P>
|
||||
async_wrapper(P ... pars) {
|
||||
get_alloc_ = [this, pars ...]()->ref_t {
|
||||
thread_local alloc_proxy tls(pars ...);
|
||||
return tls;
|
||||
};
|
||||
}
|
||||
|
||||
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:
|
||||
template <typename ... P>
|
||||
sync_wrapper(P && ... pars)
|
||||
: alloc_(std::forward<P>(pars) ...)
|
||||
{}
|
||||
|
||||
void swap(sync_wrapper& rhs) {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
|
||||
alloc_.swap(rhs.alloc_);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Variable memory allocation wrapper
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <std::size_t BaseSize = 0, std::size_t IterSize = sizeof(void*)>
|
||||
struct default_mapping_policy {
|
||||
|
||||
enum : std::size_t {
|
||||
base_size = BaseSize,
|
||||
iter_size = IterSize,
|
||||
classes_size = 64
|
||||
};
|
||||
|
||||
template <typename F, typename ... P>
|
||||
IPC_CONSTEXPR_ static void foreach(F && f, P && ... params) {
|
||||
for (std::size_t i = 0; i < classes_size; ++i) {
|
||||
f(i, std::forward<P>(params)...);
|
||||
}
|
||||
}
|
||||
|
||||
IPC_CONSTEXPR_ static std::size_t block_size(std::size_t id) noexcept {
|
||||
return (id < classes_size) ? (base_size + (id + 1) * iter_size) : 0;
|
||||
}
|
||||
|
||||
template <typename F, typename D, typename ... P>
|
||||
IPC_CONSTEXPR_ static auto classify(F && f, D && d, std::size_t size, P && ... params) {
|
||||
std::size_t id = (size - base_size - 1) / iter_size;
|
||||
return (id < classes_size) ?
|
||||
f(id, size, std::forward<P>(params)...) :
|
||||
d(size, std::forward<P>(params)...);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename FixedAlloc,
|
||||
typename DefaultAlloc = mem::static_alloc,
|
||||
typename MappingP = default_mapping_policy<>>
|
||||
class variable_wrapper {
|
||||
|
||||
struct initiator {
|
||||
|
||||
using falc_t = std::aligned_storage_t<sizeof(FixedAlloc), alignof(FixedAlloc)>;
|
||||
falc_t arr_[MappingP::classes_size];
|
||||
|
||||
initiator() {
|
||||
MappingP::foreach([](std::size_t id, falc_t * a) {
|
||||
ipc::mem::construct(&initiator::at(a, id), MappingP::block_size(id));
|
||||
}, arr_);
|
||||
}
|
||||
|
||||
~initiator() {
|
||||
MappingP::foreach([](std::size_t id, falc_t * a) {
|
||||
ipc::mem::destruct(&initiator::at(a, id));
|
||||
}, arr_);
|
||||
}
|
||||
|
||||
static FixedAlloc & at(falc_t * arr, std::size_t id) noexcept {
|
||||
return reinterpret_cast<FixedAlloc&>(arr[id]);
|
||||
}
|
||||
} init_;
|
||||
|
||||
using falc_t = typename initiator::falc_t;
|
||||
|
||||
public:
|
||||
void swap(variable_wrapper & other) {
|
||||
MappingP::foreach([](std::size_t id, falc_t * in, falc_t * ot) {
|
||||
initiator::at(in, id).swap(initiator::at(ot, id));
|
||||
}, init_.arr_, other.init_.arr_);
|
||||
}
|
||||
|
||||
void* alloc(std::size_t size) {
|
||||
return MappingP::classify([](std::size_t id, std::size_t size, falc_t * a) {
|
||||
return initiator::at(a, id).alloc(size);
|
||||
}, [](std::size_t size, falc_t *) {
|
||||
return DefaultAlloc::alloc(size);
|
||||
}, size, init_.arr_);
|
||||
}
|
||||
|
||||
void free(void* p, std::size_t size) {
|
||||
MappingP::classify([](std::size_t id, std::size_t size, void* p, falc_t * a) {
|
||||
initiator::at(a, id).free(p, size);
|
||||
}, [](std::size_t size, void* p, falc_t *) {
|
||||
DefaultAlloc::free(p, size);
|
||||
}, size, p, init_.arr_);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// 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* alloc(std::size_t size) {
|
||||
return instance().alloc(size);
|
||||
}
|
||||
|
||||
static void free(void* p, std::size_t size) {
|
||||
instance().free(p, size);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
@ -1,136 +0,0 @@
|
||||
#ifndef LIBIPC_SRC_PLATFORM_DETAIL_H_
|
||||
#define LIBIPC_SRC_PLATFORM_DETAIL_H_
|
||||
|
||||
// detect platform
|
||||
|
||||
#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || \
|
||||
defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || \
|
||||
defined(WINCE) || defined(_WIN32_WCE)
|
||||
# define IPC_OS_WINDOWS_
|
||||
#elif defined(__linux__) || defined(__linux)
|
||||
# define IPC_OS_LINUX_
|
||||
#elif defined(__QNX__)
|
||||
# define IPC_OS_QNX_
|
||||
#elif defined(__APPLE__)
|
||||
#elif defined(__ANDROID__)
|
||||
// TBD
|
||||
#endif
|
||||
|
||||
#if defined(__cplusplus)
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <type_traits>
|
||||
#include <tuple>
|
||||
#include <algorithm>
|
||||
|
||||
// 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>
|
||||
unique_ptr(T* p) -> unique_ptr<T>;
|
||||
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>
|
||||
constexpr auto unique_ptr(T* p) noexcept {
|
||||
return std::unique_ptr<T> { p };
|
||||
}
|
||||
|
||||
template <typename T, typename D>
|
||||
constexpr auto unique_ptr(T* p, D&& d) noexcept {
|
||||
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) noexcept {
|
||||
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) noexcept {
|
||||
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*/
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
|
||||
#endif // defined(__cplusplus)
|
||||
#endif // LIBIPC_SRC_PLATFORM_DETAIL_H_
|
||||
@ -1,24 +0,0 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org>
|
||||
@ -1,213 +0,0 @@
|
||||
<h1 align="center">
|
||||
<br>
|
||||
<img src="https://raw.githubusercontent.com/alephzero/logo/master/rendered/alephzero.svg" width="256px">
|
||||
<br>
|
||||
AlephZero
|
||||
</h1>
|
||||
|
||||
<h3 align="center">Simple, Robust, Fast IPC.</h3>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/alephzero/alephzero/actions?query=workflow%3ACI"><img src="https://github.com/alephzero/alephzero/workflows/CI/badge.svg"></a>
|
||||
<a href="https://codecov.io/gh/alephzero/alephzero"><img src="https://codecov.io/gh/alephzero/alephzero/branch/master/graph/badge.svg"></a>
|
||||
<a href="https://alephzero.readthedocs.io/en/latest/?badge=latest"><img src="https://readthedocs.org/projects/alephzero/badge/?version=latest"></a>
|
||||
<a href="http://unlicense.org"><img src="https://img.shields.io/badge/license-Unlicense-blue.svg"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="#overview">Overview</a> •
|
||||
<a href="#transport">Transport</a> •
|
||||
<a href="#protocol">Protocol</a> •
|
||||
<a href="#examples">Examples</a> •
|
||||
<a href="#installation">Installation</a> •
|
||||
<a href="#across-dockers">Across Dockers</a>
|
||||
</p>
|
||||
|
||||
# Overview
|
||||
|
||||
[Presentation from March 25, 2020](https://docs.google.com/presentation/d/12KE9UucjZPtpVnM1NljxOqBolBBKECWJdrCoE2yJaBw/edit#slide=id.p)
|
||||
|
||||
AlephZero is a library for message based communication between programs running on the same machine.
|
||||
|
||||
## Simple
|
||||
|
||||
AlephZero's main goal is to be simple to use. Nothing is higher priority.
|
||||
|
||||
There is no "master" process in between your nodes that is needed to do handshakes or exchanges of any kind. All you need is the topic name.
|
||||
|
||||
See the <a href="#examples">Examples</a>.
|
||||
|
||||
## Robust
|
||||
|
||||
This is probably the main value of AlephZero, above similar libraries.
|
||||
|
||||
AlephZero uses a lot of tricks to ensure the state of all channels is consistent, even when programs die. This includes double-buffering the state of the communication channel and [robustifying](https://man7.org/linux/man-pages/man3/pthread_mutexattr_setrobust.3.html) the locks and notification channels.
|
||||
|
||||
## Fast
|
||||
|
||||
AlephZero uses shared memory across multiple processes to read and write messages, minimizing the involvement of the kernel. The kernel only really gets involved in notifying a process that a new message exists, and for that we use futex (fast user-space mutex).
|
||||
|
||||
TODO: Benchmarks
|
||||
|
||||
# Transport
|
||||
|
||||
AlephZero, at its core, is a simple allocator on top of a contiguous region of memory. Usually, shared-memory. The allocator of choice is a circular-linked-list, which is fast, simple, and sufficient for the protocol listed below. It also plays well with the robustness requirement.
|
||||
|
||||
This has a number of implications. For one, this means that old messages are kept around until the space is needed. The oldest messages are always discarded before any more recent messages.
|
||||
|
||||
# Protocol
|
||||
|
||||
Rather than exposing the low-level transport directly, AlephZero provides a few higher level protocol:
|
||||
|
||||
* <b>PubSub</b>: Broadcast published messages. Subscribers get notified.
|
||||
* <b>RPC</b>: Request-response.
|
||||
* <b>PRPC (Progressive RPC)</b>: Request-streaming response.
|
||||
* <b>Sessions</b>: Bi-directional channel of communication. Not yet implemented. Let me know if you want this.
|
||||
|
||||
# Examples
|
||||
|
||||
Many more example and an interactive experience can be found at: https://github.com/alephzero/playground
|
||||
|
||||
For the curious, here are some simple snippets to get you started:
|
||||
|
||||
To begin with, we need to include AlephZero:
|
||||
```cc
|
||||
#include <a0.h>
|
||||
```
|
||||
|
||||
## PubSub
|
||||
|
||||
You can have as many publisher and subscribers on the same topic as you wish. They just need to agree on the filename.
|
||||
|
||||
```cc
|
||||
a0::Publisher p("my_pubsub_topic");
|
||||
p.pub("foo");
|
||||
```
|
||||
|
||||
You just published `"foo"` to the `"my_pubsub_topic"`.
|
||||
|
||||
To read those message, you can create a subscriber on the same topic:
|
||||
```cc
|
||||
a0::Subscriber sub(
|
||||
"my_pubsub_topic",
|
||||
A0_INIT_AWAIT_NEW, // or MOST_RECENT or OLDEST
|
||||
A0_ITER_NEWEST, // or NEXT
|
||||
[](a0::PacketView pkt_view) {
|
||||
std::cout << "Got: " << pkt_view.payload() << std::endl;
|
||||
});
|
||||
```
|
||||
The callback will trigger whenever a message is published.
|
||||
|
||||
The `Subscriber` object spawns a thread that will read the topic and call the callback.
|
||||
|
||||
The `A0_INIT` tells the subscriber where to start reading.
|
||||
* `A0_INIT_AWAIT_NEW`: Start with messages published after the creation of the subscriber.
|
||||
* `A0_INIT_MOST_RECENT`: Start with the most recently published message. Useful for state and configuration. But be careful, this can be quite old!
|
||||
* `A0_INIT_OLDEST`: Topics keep a history of 16MB (unless configures otherwise). Start with the oldest thing still in there.
|
||||
|
||||
The `A0_ITER` tells the subscriber how to continue reading messages. After each callback:
|
||||
* `A0_ITER_NEXT`: grab the sequentially next message. When you don't want to miss a thing.
|
||||
* `A0_ITER_NEWEST`: grab the newest available unread message. When you want to keep up with the firehose.
|
||||
|
||||
```cc
|
||||
a0::SubscriberSync sub_sync(
|
||||
"my_pubsub_topic",
|
||||
A0_INIT_OLDEST, A0_ITER_NEXT);
|
||||
while (sub_sync.has_next()) {
|
||||
auto pkt = sub_sync.next();
|
||||
std::cout << "Got: " << pkt.payload() << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
## RPC
|
||||
|
||||
Create an `RpcServer`:
|
||||
|
||||
```cc
|
||||
a0::RpcServer server(
|
||||
"my_rpc_topic",
|
||||
/* onrequest = */ [](a0::RpcRequest req) {
|
||||
std::cout << "Got: " << req.pkt().payload() << std::endl;
|
||||
req.reply("echo " + std::string(req.pkt().payload()));
|
||||
},
|
||||
/* oncancel = */ nullptr);
|
||||
```
|
||||
|
||||
Create an `RpcClient`:
|
||||
|
||||
```cc
|
||||
a0::RpcClient client("my_rpc_topic");
|
||||
client.send("client msg", [](a0::PacketView reply) {
|
||||
std::cout << "Got: " << reply.payload() << std::endl;
|
||||
});
|
||||
```
|
||||
|
||||
# Installation
|
||||
|
||||
## Install From Source
|
||||
|
||||
### Ubuntu Dependencies
|
||||
|
||||
```sh
|
||||
apt install g++ make
|
||||
```
|
||||
|
||||
### Alpine Dependencies
|
||||
|
||||
```sh
|
||||
apk add g++ linux-headers make
|
||||
```
|
||||
|
||||
### Download And Install
|
||||
|
||||
```sh
|
||||
git clone https://github.com/alephzero/alephzero.git
|
||||
cd alephzero
|
||||
make install -j
|
||||
```
|
||||
|
||||
## Install From Package
|
||||
|
||||
Coming soon-ish. Let me know if you want this and I'll prioritize it. External support is much appreciated.
|
||||
|
||||
## Integration
|
||||
|
||||
### Command Line
|
||||
|
||||
Add the following to g++ / clang commands.
|
||||
```sh
|
||||
-L${libdir} -lalephzero -lpthread
|
||||
```
|
||||
|
||||
### Package-cfg
|
||||
|
||||
```sh
|
||||
pkg-config --cflags --libs alephzero
|
||||
```
|
||||
|
||||
### CMake
|
||||
|
||||
Coming soon-ish. Let me know if you want this and I'll prioritize it. External support is much appreciated.
|
||||
|
||||
### Bazel
|
||||
|
||||
Coming soon-ish. Let me know if you want this and I'll prioritize it.
|
||||
|
||||
# Across Dockers
|
||||
|
||||
For programs running across different dockers to be able to communicate, we need to have them match up on two flags: `--ipc` and `--pid`.
|
||||
|
||||
* `--ipc` shares the `/dev/shm` filesystem. This is necessary to open the same file topics.
|
||||
* `--pid` shares the process id namespace. This is necessary for the locking and notification systems.
|
||||
|
||||
In the simplest case, you can set them both to `host` and talk through the system's global `/dev/shm` and process id namespace.
|
||||
```sh
|
||||
docker run --ipc=host --pid=host --name=foo foo_image
|
||||
docker run --ipc=host --pid=host --name=bar bar_image
|
||||
```
|
||||
|
||||
Or, you can mark one as `shareable` and have the others connect to it:
|
||||
```sh
|
||||
docker run --ipc=shareable --pid=shareable --name=foo foo_image
|
||||
docker run --ipc=container:foo --pid=container:foo --name=bar bar_image
|
||||
```
|
||||
@ -1,36 +0,0 @@
|
||||
#ifndef A0_SRC_ATOMIC_H
|
||||
#define A0_SRC_ATOMIC_H
|
||||
|
||||
#include "a0/inline.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
A0_STATIC_INLINE
|
||||
void a0_barrier() {
|
||||
// 'atomic_thread_fence' is not supported with ‘-fsanitize=thread’
|
||||
__sync_synchronize();
|
||||
}
|
||||
|
||||
#define a0_atomic_fetch_add(P, V) __atomic_fetch_add((P), (V), __ATOMIC_RELAXED)
|
||||
#define a0_atomic_add_fetch(P, V) __atomic_add_fetch((P), (V), __ATOMIC_RELAXED)
|
||||
|
||||
#define a0_atomic_fetch_and(P, V) __atomic_fetch_and((P), (V), __ATOMIC_RELAXED)
|
||||
#define a0_atomic_and_fetch(P, V) __atomic_and_fetch((P), (V), __ATOMIC_RELAXED)
|
||||
|
||||
#define a0_atomic_fetch_or(P, V) __atomic_fetch_or((P), (V), __ATOMIC_RELAXED)
|
||||
#define a0_atomic_or_fetch(P, V) __atomic_or_fetch((P), (V), __ATOMIC_RELAXED)
|
||||
|
||||
#define a0_atomic_load(P) __atomic_load_n((P), __ATOMIC_RELAXED)
|
||||
#define a0_atomic_store(P, V) __atomic_store_n((P), (V), __ATOMIC_RELAXED)
|
||||
|
||||
// TODO(lshamis): Switch from __sync to __atomic.
|
||||
#define a0_cas_val(P, OV, NV) __sync_val_compare_and_swap((P), (OV), (NV))
|
||||
#define a0_cas(P, OV, NV) __sync_bool_compare_and_swap((P), (OV), (NV))
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // A0_SRC_ATOMIC_H
|
||||
@ -1,60 +0,0 @@
|
||||
#ifndef A0_SRC_CLOCK_H
|
||||
#define A0_SRC_CLOCK_H
|
||||
|
||||
#include "a0/err.h"
|
||||
#include "a0/inline.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "err_macro.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
static const int64_t NS_PER_SEC = 1e9;
|
||||
|
||||
typedef struct timespec timespec_t;
|
||||
|
||||
A0_STATIC_INLINE
|
||||
a0_err_t a0_clock_now(clockid_t clk, timespec_t* out) {
|
||||
A0_RETURN_SYSERR_ON_MINUS_ONE(clock_gettime(clk, out));
|
||||
return A0_OK;
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
a0_err_t a0_clock_add(timespec_t ts, int64_t add_nsec, timespec_t* out) {
|
||||
out->tv_sec = ts.tv_sec + add_nsec / NS_PER_SEC;
|
||||
out->tv_nsec = ts.tv_nsec + add_nsec % NS_PER_SEC;
|
||||
if (out->tv_nsec >= NS_PER_SEC) {
|
||||
out->tv_sec++;
|
||||
out->tv_nsec -= NS_PER_SEC;
|
||||
} else if (out->tv_nsec < 0) {
|
||||
out->tv_sec--;
|
||||
out->tv_nsec += NS_PER_SEC;
|
||||
}
|
||||
|
||||
return A0_OK;
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
a0_err_t a0_clock_convert(
|
||||
clockid_t orig_clk,
|
||||
timespec_t orig_ts,
|
||||
clockid_t target_clk,
|
||||
timespec_t* target_ts) {
|
||||
timespec_t orig_now;
|
||||
A0_RETURN_ERR_ON_ERR(a0_clock_now(orig_clk, &orig_now));
|
||||
timespec_t target_now;
|
||||
A0_RETURN_ERR_ON_ERR(a0_clock_now(target_clk, &target_now));
|
||||
|
||||
int64_t add_nsec = (orig_ts.tv_sec - orig_now.tv_sec) * NS_PER_SEC + (orig_ts.tv_nsec - orig_now.tv_nsec);
|
||||
return a0_clock_add(target_now, add_nsec, target_ts);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // A0_SRC_CLOCK_H
|
||||
@ -1,13 +0,0 @@
|
||||
#ifndef A0_EMPTY_H
|
||||
#define A0_EMPTY_H
|
||||
|
||||
// Bah. Why is there no consistent way to zero initialize a struct?
|
||||
#ifdef __cplusplus
|
||||
#define A0_EMPTY \
|
||||
{}
|
||||
#else
|
||||
#define A0_EMPTY \
|
||||
{ 0 }
|
||||
#endif
|
||||
|
||||
#endif // A0_EMPTY_H
|
||||
@ -1,50 +0,0 @@
|
||||
#include "a0/err.h"
|
||||
#include "a0/thread_local.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
A0_THREAD_LOCAL int a0_err_syscode;
|
||||
A0_THREAD_LOCAL char a0_err_msg[1024];
|
||||
|
||||
const char* a0_strerror(a0_err_t err) {
|
||||
switch (err) {
|
||||
case A0_OK: {
|
||||
return strerror(0);
|
||||
}
|
||||
case A0_ERR_SYS: {
|
||||
return strerror(a0_err_syscode);
|
||||
}
|
||||
case A0_ERR_CUSTOM_MSG: {
|
||||
return a0_err_msg;
|
||||
}
|
||||
case A0_ERR_INVALID_ARG: {
|
||||
return strerror(EINVAL);
|
||||
}
|
||||
case A0_ERR_RANGE: {
|
||||
return "Index out of bounds";
|
||||
}
|
||||
case A0_ERR_AGAIN: {
|
||||
return "Not available yet";
|
||||
}
|
||||
case A0_ERR_FRAME_LARGE: {
|
||||
return "Frame size too large";
|
||||
}
|
||||
case A0_ERR_ITER_DONE: {
|
||||
return "Done iterating";
|
||||
}
|
||||
case A0_ERR_NOT_FOUND: {
|
||||
return "Not found";
|
||||
}
|
||||
case A0_ERR_BAD_PATH: {
|
||||
return "Invalid path";
|
||||
}
|
||||
case A0_ERR_BAD_TOPIC: {
|
||||
return "Invalid topic name";
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
#ifndef A0_ERR_H
|
||||
#define A0_ERR_H
|
||||
|
||||
#include "a0/thread_local.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum a0_err_e {
|
||||
A0_OK = 0,
|
||||
A0_ERR_SYS = 1,
|
||||
A0_ERR_CUSTOM_MSG = 2,
|
||||
A0_ERR_INVALID_ARG = 3,
|
||||
A0_ERR_RANGE = 4,
|
||||
A0_ERR_AGAIN = 5,
|
||||
A0_ERR_ITER_DONE = 6,
|
||||
A0_ERR_NOT_FOUND = 7,
|
||||
A0_ERR_FRAME_LARGE = 8,
|
||||
A0_ERR_BAD_PATH = 9,
|
||||
A0_ERR_BAD_TOPIC = 10,
|
||||
} a0_err_t;
|
||||
|
||||
extern A0_THREAD_LOCAL int a0_err_syscode;
|
||||
extern A0_THREAD_LOCAL char a0_err_msg[1024];
|
||||
|
||||
const char* a0_strerror(a0_err_t);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // A0_ERR_H
|
||||
@ -1,52 +0,0 @@
|
||||
#ifndef A0_SRC_ERR_MACRO_H
|
||||
#define A0_SRC_ERR_MACRO_H
|
||||
|
||||
#include "a0/err.h"
|
||||
#include "a0/inline.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
A0_STATIC_INLINE
|
||||
a0_err_t A0_MAKE_SYSERR(int syserr) {
|
||||
a0_err_syscode = syserr;
|
||||
return A0_ERR_SYS;
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
int A0_SYSERR(a0_err_t err) {
|
||||
return err == A0_ERR_SYS ? a0_err_syscode : 0;
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE_RECURSIVE
|
||||
a0_err_t A0_MAKE_MSGERR(const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
if (fmt) {
|
||||
vsnprintf(a0_err_msg, sizeof(a0_err_msg), fmt, args); // NOLINT(clang-analyzer-valist.Uninitialized): https://bugs.llvm.org/show_bug.cgi?id=41311
|
||||
va_end(args);
|
||||
return A0_ERR_CUSTOM_MSG;
|
||||
}
|
||||
va_end(args);
|
||||
return A0_OK;
|
||||
}
|
||||
|
||||
#define A0_RETURN_SYSERR_ON_MINUS_ONE(X) \
|
||||
do { \
|
||||
if ((X) == -1) { \
|
||||
a0_err_syscode = errno; \
|
||||
return A0_ERR_SYS; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define A0_RETURN_ERR_ON_ERR(X) \
|
||||
do { \
|
||||
a0_err_t _err = (X); \
|
||||
if (_err) { \
|
||||
return _err; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#endif // A0_SRC_ERR_MACRO_H
|
||||
@ -1,111 +0,0 @@
|
||||
#ifndef A0_SRC_FTX_H
|
||||
#define A0_SRC_FTX_H
|
||||
|
||||
#include "a0/err.h"
|
||||
#include "a0/inline.h"
|
||||
#include "a0/time.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <linux/futex.h>
|
||||
#include <stdint.h>
|
||||
#include <syscall.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "clock.h"
|
||||
#include "err_macro.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// FUTEX_WAIT and FUTEX_WAIT_REQUEUE_PI default to CLOCK_MONOTONIC,
|
||||
// but FUTEX_LOCK_PI always uses CLOCK_REALTIME.
|
||||
//
|
||||
// Until someone tells me otherwise, I assume this is bad decision making
|
||||
// and I will instead standardize all things on CLOCK_BOOTTIME.
|
||||
|
||||
// Futex.
|
||||
// Operations rely on the address.
|
||||
// It should not be copied or moved.
|
||||
typedef uint32_t a0_ftx_t;
|
||||
|
||||
A0_STATIC_INLINE
|
||||
a0_err_t a0_futex(a0_ftx_t* uaddr,
|
||||
int futex_op,
|
||||
int val,
|
||||
uintptr_t timeout_or_val2,
|
||||
a0_ftx_t* uaddr2,
|
||||
int val3) {
|
||||
A0_RETURN_SYSERR_ON_MINUS_ONE(syscall(SYS_futex, uaddr, futex_op, val, timeout_or_val2, uaddr2, val3));
|
||||
return A0_OK;
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
a0_err_t a0_ftx_wait(a0_ftx_t* ftx, int confirm_val, const a0_time_mono_t* time_mono) {
|
||||
if (!time_mono) {
|
||||
return a0_futex(ftx, FUTEX_WAIT, confirm_val, 0, NULL, 0);
|
||||
}
|
||||
|
||||
timespec_t ts_mono;
|
||||
A0_RETURN_ERR_ON_ERR(a0_clock_convert(CLOCK_BOOTTIME, time_mono->ts, CLOCK_MONOTONIC, &ts_mono));
|
||||
return a0_futex(ftx, FUTEX_WAIT, confirm_val, (uintptr_t)&ts_mono, NULL, 0);
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
a0_err_t a0_ftx_wake(a0_ftx_t* ftx, int cnt) {
|
||||
return a0_futex(ftx, FUTEX_WAKE, cnt, 0, NULL, 0);
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
a0_err_t a0_ftx_signal(a0_ftx_t* ftx) {
|
||||
return a0_ftx_wake(ftx, 1);
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
a0_err_t a0_ftx_broadcast(a0_ftx_t* ftx) {
|
||||
return a0_ftx_wake(ftx, INT_MAX);
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
a0_err_t a0_ftx_lock_pi(a0_ftx_t* ftx, const a0_time_mono_t* time_mono) {
|
||||
if (!time_mono) {
|
||||
return a0_futex(ftx, FUTEX_LOCK_PI, 0, 0, NULL, 0);
|
||||
}
|
||||
|
||||
timespec_t ts_wall;
|
||||
A0_RETURN_ERR_ON_ERR(a0_clock_convert(CLOCK_BOOTTIME, time_mono->ts, CLOCK_REALTIME, &ts_wall));
|
||||
return a0_futex(ftx, FUTEX_LOCK_PI, 0, (uintptr_t)&ts_wall, NULL, 0);
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
a0_err_t a0_ftx_trylock_pi(a0_ftx_t* ftx) {
|
||||
return a0_futex(ftx, FUTEX_TRYLOCK_PI, 0, 0, NULL, 0);
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
a0_err_t a0_ftx_unlock_pi(a0_ftx_t* ftx) {
|
||||
return a0_futex(ftx, FUTEX_UNLOCK_PI, 0, 0, NULL, 0);
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
a0_err_t a0_ftx_cmp_requeue_pi(a0_ftx_t* ftx, int confirm_val, a0_ftx_t* requeue_ftx, int max_requeue) {
|
||||
return a0_futex(ftx, FUTEX_CMP_REQUEUE_PI, 1, max_requeue, requeue_ftx, confirm_val);
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
a0_err_t a0_ftx_wait_requeue_pi(a0_ftx_t* ftx, int confirm_val, const a0_time_mono_t* time_mono, a0_ftx_t* requeue_ftx) {
|
||||
if (!time_mono) {
|
||||
return a0_futex(ftx, FUTEX_WAIT_REQUEUE_PI, confirm_val, 0, requeue_ftx, 0);
|
||||
}
|
||||
|
||||
timespec_t ts_mono;
|
||||
A0_RETURN_ERR_ON_ERR(a0_clock_convert(CLOCK_BOOTTIME, time_mono->ts, CLOCK_MONOTONIC, &ts_mono));
|
||||
return a0_futex(ftx, FUTEX_WAIT_REQUEUE_PI, confirm_val, (uintptr_t)&ts_mono, requeue_ftx, 0);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // A0_SRC_FTX_H
|
||||
@ -1,8 +0,0 @@
|
||||
#ifndef A0_INLINE_H
|
||||
#define A0_INLINE_H
|
||||
|
||||
#define A0_STATIC_INLINE static inline __attribute__((always_inline))
|
||||
|
||||
#define A0_STATIC_INLINE_RECURSIVE static inline
|
||||
|
||||
#endif // A0_INLINE_H
|
||||
@ -1,420 +0,0 @@
|
||||
#include "a0/err.h"
|
||||
#include "a0/inline.h"
|
||||
#include "a0/mtx.h"
|
||||
#include "a0/thread_local.h"
|
||||
#include "a0/tid.h"
|
||||
#include "a0/time.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <linux/futex.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <syscall.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "atomic.h"
|
||||
#include "clock.h"
|
||||
#include "err_macro.h"
|
||||
#include "ftx.h"
|
||||
|
||||
// TSAN is worth the pain of properly annotating our mutex.
|
||||
|
||||
// clang-format off
|
||||
#if defined(__SANITIZE_THREAD__)
|
||||
#define A0_TSAN_ENABLED
|
||||
#elif defined(__has_feature)
|
||||
#if __has_feature(thread_sanitizer)
|
||||
#define A0_TSAN_ENABLED
|
||||
#endif
|
||||
#endif
|
||||
// clang-format on
|
||||
|
||||
const unsigned __tsan_mutex_linker_init = 1 << 0;
|
||||
const unsigned __tsan_mutex_write_reentrant = 1 << 1;
|
||||
const unsigned __tsan_mutex_read_reentrant = 1 << 2;
|
||||
const unsigned __tsan_mutex_not_static = 1 << 8;
|
||||
const unsigned __tsan_mutex_read_lock = 1 << 3;
|
||||
const unsigned __tsan_mutex_try_lock = 1 << 4;
|
||||
const unsigned __tsan_mutex_try_lock_failed = 1 << 5;
|
||||
const unsigned __tsan_mutex_recursive_lock = 1 << 6;
|
||||
const unsigned __tsan_mutex_recursive_unlock = 1 << 7;
|
||||
|
||||
#ifdef A0_TSAN_ENABLED
|
||||
|
||||
void __tsan_mutex_create(void* addr, unsigned flags);
|
||||
void __tsan_mutex_destroy(void* addr, unsigned flags);
|
||||
void __tsan_mutex_pre_lock(void* addr, unsigned flags);
|
||||
void __tsan_mutex_post_lock(void* addr, unsigned flags, int recursion);
|
||||
int __tsan_mutex_pre_unlock(void* addr, unsigned flags);
|
||||
void __tsan_mutex_post_unlock(void* addr, unsigned flags);
|
||||
void __tsan_mutex_pre_signal(void* addr, unsigned flags);
|
||||
void __tsan_mutex_post_signal(void* addr, unsigned flags);
|
||||
void __tsan_mutex_pre_divert(void* addr, unsigned flags);
|
||||
void __tsan_mutex_post_divert(void* addr, unsigned flags);
|
||||
|
||||
#else
|
||||
|
||||
#define _u_ __attribute__((unused))
|
||||
|
||||
A0_STATIC_INLINE void _u_ __tsan_mutex_create(_u_ void* addr, _u_ unsigned flags) {}
|
||||
A0_STATIC_INLINE void _u_ __tsan_mutex_destroy(_u_ void* addr, _u_ unsigned flags) {}
|
||||
A0_STATIC_INLINE void _u_ __tsan_mutex_pre_lock(_u_ void* addr, _u_ unsigned flags) {}
|
||||
A0_STATIC_INLINE void _u_ __tsan_mutex_post_lock(_u_ void* addr,
|
||||
_u_ unsigned flags,
|
||||
_u_ int recursion) {}
|
||||
A0_STATIC_INLINE int _u_ __tsan_mutex_pre_unlock(_u_ void* addr, _u_ unsigned flags) {
|
||||
return 0;
|
||||
}
|
||||
A0_STATIC_INLINE void _u_ __tsan_mutex_post_unlock(_u_ void* addr, _u_ unsigned flags) {}
|
||||
A0_STATIC_INLINE void _u_ __tsan_mutex_pre_signal(_u_ void* addr, _u_ unsigned flags) {}
|
||||
A0_STATIC_INLINE void _u_ __tsan_mutex_post_signal(_u_ void* addr, _u_ unsigned flags) {}
|
||||
A0_STATIC_INLINE void _u_ __tsan_mutex_pre_divert(_u_ void* addr, _u_ unsigned flags) {}
|
||||
A0_STATIC_INLINE void _u_ __tsan_mutex_post_divert(_u_ void* addr, _u_ unsigned flags) {}
|
||||
|
||||
#endif
|
||||
|
||||
A0_THREAD_LOCAL bool a0_robust_init = false;
|
||||
|
||||
A0_STATIC_INLINE
|
||||
void a0_robust_reset() {
|
||||
a0_robust_init = 0;
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
void a0_robust_reset_atfork() {
|
||||
pthread_atfork(NULL, NULL, &a0_robust_reset);
|
||||
}
|
||||
|
||||
static pthread_once_t a0_robust_reset_atfork_once;
|
||||
|
||||
typedef struct robust_list robust_list_t;
|
||||
typedef struct robust_list_head robust_list_head_t;
|
||||
|
||||
A0_THREAD_LOCAL robust_list_head_t a0_robust_head;
|
||||
|
||||
A0_STATIC_INLINE
|
||||
void robust_init() {
|
||||
a0_robust_head.list.next = &a0_robust_head.list;
|
||||
a0_robust_head.futex_offset = offsetof(a0_mtx_t, ftx);
|
||||
a0_robust_head.list_op_pending = NULL;
|
||||
syscall(SYS_set_robust_list, &a0_robust_head.list, sizeof(a0_robust_head));
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
void init_thread() {
|
||||
if (a0_robust_init) {
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_once(&a0_robust_reset_atfork_once, a0_robust_reset_atfork);
|
||||
robust_init();
|
||||
a0_robust_init = true;
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
void robust_op_start(a0_mtx_t* mtx) {
|
||||
init_thread();
|
||||
a0_robust_head.list_op_pending = (struct robust_list*)mtx;
|
||||
a0_barrier();
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
void robust_op_end(a0_mtx_t* mtx) {
|
||||
(void)mtx;
|
||||
a0_barrier();
|
||||
a0_robust_head.list_op_pending = NULL;
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
bool robust_is_head(a0_mtx_t* mtx) {
|
||||
return mtx == (a0_mtx_t*)&a0_robust_head;
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
void robust_op_add(a0_mtx_t* mtx) {
|
||||
a0_mtx_t* old_first = (a0_mtx_t*)a0_robust_head.list.next;
|
||||
|
||||
mtx->prev = (a0_mtx_t*)&a0_robust_head;
|
||||
mtx->next = old_first;
|
||||
|
||||
a0_barrier();
|
||||
|
||||
a0_robust_head.list.next = (robust_list_t*)mtx;
|
||||
if (!robust_is_head(old_first)) {
|
||||
old_first->prev = mtx;
|
||||
}
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
void robust_op_del(a0_mtx_t* mtx) {
|
||||
a0_mtx_t* prev = mtx->prev;
|
||||
a0_mtx_t* next = mtx->next;
|
||||
prev->next = next;
|
||||
if (!robust_is_head(next)) {
|
||||
next->prev = prev;
|
||||
}
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
uint32_t ftx_tid(a0_ftx_t ftx) {
|
||||
return ftx & FUTEX_TID_MASK;
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
bool ftx_owner_died(a0_ftx_t ftx) {
|
||||
return ftx & FUTEX_OWNER_DIED;
|
||||
}
|
||||
|
||||
static const uint32_t FTX_NOTRECOVERABLE = FUTEX_TID_MASK | FUTEX_OWNER_DIED;
|
||||
|
||||
A0_STATIC_INLINE
|
||||
bool ftx_notrecoverable(a0_ftx_t ftx) {
|
||||
return (ftx & FTX_NOTRECOVERABLE) == FTX_NOTRECOVERABLE;
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
a0_err_t a0_mtx_timedlock_robust(a0_mtx_t* mtx, const a0_time_mono_t* timeout) {
|
||||
const uint32_t tid = a0_tid();
|
||||
|
||||
int syserr = EINTR;
|
||||
while (syserr == EINTR) {
|
||||
// Can't lock if borked.
|
||||
if (ftx_notrecoverable(a0_atomic_load(&mtx->ftx))) {
|
||||
return A0_MAKE_SYSERR(ENOTRECOVERABLE);
|
||||
}
|
||||
|
||||
// Try to lock without kernel involvement.
|
||||
if (a0_cas(&mtx->ftx, 0, tid)) {
|
||||
return A0_OK;
|
||||
}
|
||||
|
||||
// Ask the kernel to lock.
|
||||
syserr = A0_SYSERR(a0_ftx_lock_pi(&mtx->ftx, timeout));
|
||||
}
|
||||
|
||||
if (!syserr) {
|
||||
if (ftx_owner_died(a0_atomic_load(&mtx->ftx))) {
|
||||
return A0_MAKE_SYSERR(EOWNERDEAD);
|
||||
}
|
||||
return A0_OK;
|
||||
}
|
||||
|
||||
return A0_MAKE_SYSERR(syserr);
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
a0_err_t a0_mtx_timedlock_impl(a0_mtx_t* mtx, const a0_time_mono_t* timeout) {
|
||||
// Note: __tsan_mutex_pre_lock should come here, but tsan doesn't provide
|
||||
// a way to "fail" a lock. Only a trylock.
|
||||
robust_op_start(mtx);
|
||||
const a0_err_t err = a0_mtx_timedlock_robust(mtx, timeout);
|
||||
if (!err || A0_SYSERR(err) == EOWNERDEAD) {
|
||||
__tsan_mutex_pre_lock(mtx, 0);
|
||||
robust_op_add(mtx);
|
||||
__tsan_mutex_post_lock(mtx, 0, 0);
|
||||
}
|
||||
robust_op_end(mtx);
|
||||
return err;
|
||||
}
|
||||
|
||||
a0_err_t a0_mtx_timedlock(a0_mtx_t* mtx, a0_time_mono_t timeout) {
|
||||
return a0_mtx_timedlock_impl(mtx, &timeout);
|
||||
}
|
||||
|
||||
a0_err_t a0_mtx_lock(a0_mtx_t* mtx) {
|
||||
return a0_mtx_timedlock_impl(mtx, NULL);
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
a0_err_t a0_mtx_trylock_impl(a0_mtx_t* mtx) {
|
||||
const uint32_t tid = a0_tid();
|
||||
|
||||
// Try to lock without kernel involvement.
|
||||
uint32_t old = a0_cas_val(&mtx->ftx, 0, tid);
|
||||
|
||||
// Did it work?
|
||||
if (!old) {
|
||||
robust_op_add(mtx);
|
||||
return A0_OK;
|
||||
}
|
||||
|
||||
// Is the lock still usable?
|
||||
if (ftx_notrecoverable(old)) {
|
||||
return A0_MAKE_SYSERR(ENOTRECOVERABLE);
|
||||
}
|
||||
|
||||
// Is the owner still alive?
|
||||
if (!ftx_owner_died(old)) {
|
||||
return A0_MAKE_SYSERR(EBUSY);
|
||||
}
|
||||
|
||||
// Oh, the owner died. Ask the kernel to fix the state.
|
||||
a0_err_t err = a0_ftx_trylock_pi(&mtx->ftx);
|
||||
if (!err) {
|
||||
robust_op_add(mtx);
|
||||
if (ftx_owner_died(a0_atomic_load(&mtx->ftx))) {
|
||||
return A0_MAKE_SYSERR(EOWNERDEAD);
|
||||
}
|
||||
return A0_OK;
|
||||
}
|
||||
|
||||
// EAGAIN means that somebody else beat us to it.
|
||||
// Anything else means we're borked.
|
||||
if (A0_SYSERR(err) == EAGAIN) {
|
||||
return A0_MAKE_SYSERR(EBUSY);
|
||||
}
|
||||
return A0_MAKE_SYSERR(ENOTRECOVERABLE);
|
||||
}
|
||||
|
||||
a0_err_t a0_mtx_trylock(a0_mtx_t* mtx) {
|
||||
__tsan_mutex_pre_lock(mtx, __tsan_mutex_try_lock);
|
||||
robust_op_start(mtx);
|
||||
a0_err_t err = a0_mtx_trylock_impl(mtx);
|
||||
robust_op_end(mtx);
|
||||
if (!err || A0_SYSERR(err) == EOWNERDEAD) {
|
||||
__tsan_mutex_post_lock(mtx, __tsan_mutex_try_lock, 0);
|
||||
} else {
|
||||
__tsan_mutex_post_lock(mtx, __tsan_mutex_try_lock | __tsan_mutex_try_lock_failed, 0);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
a0_err_t a0_mtx_consistent(a0_mtx_t* mtx) {
|
||||
const uint32_t val = a0_atomic_load(&mtx->ftx);
|
||||
|
||||
// Why fix what isn't broken?
|
||||
if (!ftx_owner_died(val)) {
|
||||
return A0_MAKE_SYSERR(EINVAL);
|
||||
}
|
||||
|
||||
// Is it yours to fix?
|
||||
if (ftx_tid(val) != a0_tid()) {
|
||||
return A0_MAKE_SYSERR(EPERM);
|
||||
}
|
||||
|
||||
// Fix it!
|
||||
a0_atomic_and_fetch(&mtx->ftx, ~FUTEX_OWNER_DIED);
|
||||
|
||||
return A0_OK;
|
||||
}
|
||||
|
||||
a0_err_t a0_mtx_unlock(a0_mtx_t* mtx) {
|
||||
const uint32_t tid = a0_tid();
|
||||
|
||||
const uint32_t val = a0_atomic_load(&mtx->ftx);
|
||||
|
||||
// Only the owner can unlock.
|
||||
if (ftx_tid(val) != tid) {
|
||||
return A0_MAKE_SYSERR(EPERM);
|
||||
}
|
||||
|
||||
__tsan_mutex_pre_unlock(mtx, 0);
|
||||
|
||||
// If the mutex was acquired with EOWNERDEAD, the caller is responsible
|
||||
// for fixing the state and marking the mutex consistent. If they did
|
||||
// not mark it consistent and are unlocking... then we are unrecoverably
|
||||
// borked!
|
||||
uint32_t new_val = 0;
|
||||
if (ftx_owner_died(val)) {
|
||||
new_val = FTX_NOTRECOVERABLE;
|
||||
}
|
||||
|
||||
robust_op_start(mtx);
|
||||
robust_op_del(mtx);
|
||||
|
||||
// If the futex is exactly equal to tid, then there are no waiters and the
|
||||
// kernel doesn't need to get involved.
|
||||
if (!a0_cas(&mtx->ftx, tid, new_val)) {
|
||||
// Ask the kernel to wake up a waiter.
|
||||
a0_ftx_unlock_pi(&mtx->ftx);
|
||||
if (new_val) {
|
||||
a0_atomic_or_fetch(&mtx->ftx, new_val);
|
||||
}
|
||||
}
|
||||
|
||||
robust_op_end(mtx);
|
||||
__tsan_mutex_post_unlock(mtx, 0);
|
||||
|
||||
return A0_OK;
|
||||
}
|
||||
|
||||
// TODO(lshamis): Handle ENOTRECOVERABLE
|
||||
A0_STATIC_INLINE
|
||||
a0_err_t a0_cnd_timedwait_impl(a0_cnd_t* cnd, a0_mtx_t* mtx, const a0_time_mono_t* timeout) {
|
||||
const uint32_t init_cnd = a0_atomic_load(cnd);
|
||||
|
||||
// Unblock other threads to do the things that will eventually signal this wait.
|
||||
a0_err_t err = a0_mtx_unlock(mtx);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
__tsan_mutex_pre_lock(mtx, 0);
|
||||
robust_op_start(mtx);
|
||||
|
||||
do {
|
||||
// Priority-inheritance-aware wait until awoken or timeout.
|
||||
err = a0_ftx_wait_requeue_pi(cnd, init_cnd, timeout, &mtx->ftx);
|
||||
} while (A0_SYSERR(err) == EINTR);
|
||||
|
||||
// We need to manually lock on timeout.
|
||||
// Note: We keep the timeout error.
|
||||
if (A0_SYSERR(err) == ETIMEDOUT) {
|
||||
a0_mtx_timedlock_robust(mtx, NULL);
|
||||
}
|
||||
// Someone else grabbed and mutated the resource between the unlock and wait.
|
||||
// No need to wait.
|
||||
if (A0_SYSERR(err) == EAGAIN) {
|
||||
err = a0_mtx_timedlock_robust(mtx, NULL);
|
||||
}
|
||||
|
||||
robust_op_add(mtx);
|
||||
|
||||
// If no higher priority error, check the previous owner didn't die.
|
||||
if (!err) {
|
||||
err = ftx_owner_died(a0_atomic_load(&mtx->ftx)) ? EOWNERDEAD : A0_OK;
|
||||
}
|
||||
|
||||
robust_op_end(mtx);
|
||||
__tsan_mutex_post_lock(mtx, 0, 0);
|
||||
return err;
|
||||
}
|
||||
|
||||
a0_err_t a0_cnd_timedwait(a0_cnd_t* cnd, a0_mtx_t* mtx, a0_time_mono_t timeout) {
|
||||
// Let's not unlock the mutex if we're going to get EINVAL due to a bad timeout.
|
||||
if ((timeout.ts.tv_sec < 0 || timeout.ts.tv_nsec < 0 || (!timeout.ts.tv_sec && !timeout.ts.tv_nsec) || timeout.ts.tv_nsec >= NS_PER_SEC)) {
|
||||
return A0_MAKE_SYSERR(EINVAL);
|
||||
}
|
||||
return a0_cnd_timedwait_impl(cnd, mtx, &timeout);
|
||||
}
|
||||
|
||||
a0_err_t a0_cnd_wait(a0_cnd_t* cnd, a0_mtx_t* mtx) {
|
||||
return a0_cnd_timedwait_impl(cnd, mtx, NULL);
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
a0_err_t a0_cnd_wake(a0_cnd_t* cnd, a0_mtx_t* mtx, uint32_t cnt) {
|
||||
uint32_t val = a0_atomic_add_fetch(cnd, 1);
|
||||
|
||||
while (true) {
|
||||
a0_err_t err = a0_ftx_cmp_requeue_pi(cnd, val, &mtx->ftx, cnt);
|
||||
if (A0_SYSERR(err) != EAGAIN) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// Another thread is also trying to wake this condition variable.
|
||||
val = a0_atomic_load(cnd);
|
||||
}
|
||||
}
|
||||
|
||||
a0_err_t a0_cnd_signal(a0_cnd_t* cnd, a0_mtx_t* mtx) {
|
||||
return a0_cnd_wake(cnd, mtx, 1);
|
||||
}
|
||||
|
||||
a0_err_t a0_cnd_broadcast(a0_cnd_t* cnd, a0_mtx_t* mtx) {
|
||||
return a0_cnd_wake(cnd, mtx, INT_MAX);
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
#ifndef A0_MTX_H
|
||||
#define A0_MTX_H
|
||||
|
||||
#include "a0/err.h"
|
||||
#include "a0/time.h"
|
||||
#include "a0/unused.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef uint32_t a0_ftx_t;
|
||||
|
||||
// https://stackoverflow.com/questions/61645966/is-typedef-allowed-before-definition
|
||||
struct a0_mtx_s;
|
||||
|
||||
typedef struct a0_mtx_s a0_mtx_t;
|
||||
|
||||
// Mutex implementation designed for IPC.
|
||||
//
|
||||
// Similar to pthread_mutex_t with the following flags fixed:
|
||||
// * Process shared.
|
||||
// * Robust.
|
||||
// * Error checking.
|
||||
// * Priority inheriting.
|
||||
//
|
||||
// Unlike pthread_mutex_t, timespec are expected to use CLOCK_BOOTTIME.
|
||||
//
|
||||
// struct a0_mtx_s "Inherits" from robust_list, which requires:
|
||||
// * The first field MUST be a next pointer.
|
||||
// * There must be a futex, which makes the mutex immovable.
|
||||
//
|
||||
// Note: a mutex MUST be unlocked before being freed or unmapped.
|
||||
struct a0_mtx_s {
|
||||
a0_mtx_t* next;
|
||||
a0_mtx_t* prev;
|
||||
a0_ftx_t ftx;
|
||||
};
|
||||
|
||||
a0_err_t a0_mtx_lock(a0_mtx_t*) A0_WARN_UNUSED_RESULT;
|
||||
a0_err_t a0_mtx_timedlock(a0_mtx_t*, a0_time_mono_t) A0_WARN_UNUSED_RESULT;
|
||||
a0_err_t a0_mtx_trylock(a0_mtx_t*) A0_WARN_UNUSED_RESULT;
|
||||
a0_err_t a0_mtx_consistent(a0_mtx_t*);
|
||||
a0_err_t a0_mtx_unlock(a0_mtx_t*);
|
||||
|
||||
typedef a0_ftx_t a0_cnd_t;
|
||||
|
||||
a0_err_t a0_cnd_wait(a0_cnd_t*, a0_mtx_t*);
|
||||
a0_err_t a0_cnd_timedwait(a0_cnd_t*, a0_mtx_t*, a0_time_mono_t);
|
||||
a0_err_t a0_cnd_signal(a0_cnd_t*, a0_mtx_t*);
|
||||
a0_err_t a0_cnd_broadcast(a0_cnd_t*, a0_mtx_t*);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // A0_MTX_H
|
||||
@ -1,64 +0,0 @@
|
||||
#include "strconv.h"
|
||||
|
||||
#include "a0/err.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static const char DECIMAL_DIGITS[] =
|
||||
"00010203040506070809"
|
||||
"10111213141516171819"
|
||||
"20212223242526272829"
|
||||
"30313233343536373839"
|
||||
"40414243444546474849"
|
||||
"50515253545556575859"
|
||||
"60616263646566676869"
|
||||
"70717273747576777879"
|
||||
"80818283848586878889"
|
||||
"90919293949596979899";
|
||||
|
||||
a0_err_t a0_u32_to_str(uint32_t val, char* buf_start, char* buf_end, char** start_ptr) {
|
||||
return a0_u64_to_str(val, buf_start, buf_end, start_ptr);
|
||||
}
|
||||
|
||||
a0_err_t a0_u64_to_str(uint64_t val, char* buf_start, char* buf_end, char** start_ptr) {
|
||||
uint64_t orig_val = val;
|
||||
char* ptr = buf_end;
|
||||
while (val >= 10) {
|
||||
ptr -= 2;
|
||||
memcpy(ptr, &DECIMAL_DIGITS[2 * (val % 100)], sizeof(uint16_t));
|
||||
val /= 100;
|
||||
}
|
||||
if (val) {
|
||||
*--ptr = (char)('0' + val);
|
||||
}
|
||||
memset(buf_start, '0', ptr - buf_start);
|
||||
ptr -= (!orig_val);
|
||||
if (start_ptr) {
|
||||
*start_ptr = ptr;
|
||||
}
|
||||
return A0_OK;
|
||||
}
|
||||
|
||||
a0_err_t a0_str_to_u32(const char* start, const char* end, uint32_t* out) {
|
||||
*out = 0;
|
||||
for (const char* ptr = start; ptr < end; ptr++) {
|
||||
if (*ptr < '0' || *ptr > '9') {
|
||||
return A0_ERR_INVALID_ARG;
|
||||
}
|
||||
*out *= 10;
|
||||
*out += *ptr - '0';
|
||||
}
|
||||
return A0_OK;
|
||||
}
|
||||
|
||||
a0_err_t a0_str_to_u64(const char* start, const char* end, uint64_t* out) {
|
||||
*out = 0;
|
||||
for (const char* ptr = start; ptr < end; ptr++) {
|
||||
if (*ptr < '0' || *ptr > '9') {
|
||||
return A0_ERR_INVALID_ARG;
|
||||
}
|
||||
*out *= 10;
|
||||
*out += *ptr - '0';
|
||||
}
|
||||
return A0_OK;
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
#ifndef A0_SRC_STRCONV_H
|
||||
#define A0_SRC_STRCONV_H
|
||||
|
||||
#include "a0/err.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Converts a uint32 or uint64 to string.
|
||||
// The entire buffer will be populated, if not with given the value, then with '0'.
|
||||
// Populates the buffer from the back.
|
||||
// start_ptr, if not null, will be set to the point within the buffer where the number starts.
|
||||
// Does NOT check for overflow.
|
||||
a0_err_t a0_u32_to_str(uint32_t val, char* buf_start, char* buf_end, char** start_ptr);
|
||||
a0_err_t a0_u64_to_str(uint64_t val, char* buf_start, char* buf_end, char** start_ptr);
|
||||
|
||||
// Converts a string to uint32 or uint64.
|
||||
// The string may have leading '0's.
|
||||
// Returns A0_ERR_INVALID_ARG if any character is not a digit.
|
||||
// Does NOT check for overflow.
|
||||
a0_err_t a0_str_to_u32(const char* start, const char* end, uint32_t* out);
|
||||
a0_err_t a0_str_to_u64(const char* start, const char* end, uint64_t* out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // A0_SRC_STRCONV_H
|
||||
@ -1,10 +0,0 @@
|
||||
#ifndef A0_THREAD_LOCAL_H
|
||||
#define A0_THREAD_LOCAL_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define A0_THREAD_LOCAL thread_local
|
||||
#else
|
||||
#define A0_THREAD_LOCAL _Thread_local
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif // A0_THREAD_LOCAL_H
|
||||
@ -1,30 +0,0 @@
|
||||
#include "a0/inline.h"
|
||||
#include "a0/thread_local.h"
|
||||
#include "a0/tid.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
A0_THREAD_LOCAL uint32_t a0_tid_cache = 0;
|
||||
static pthread_once_t a0_tid_reset_atfork_once;
|
||||
|
||||
A0_STATIC_INLINE
|
||||
void a0_tid_reset() {
|
||||
a0_tid_cache = 0;
|
||||
}
|
||||
|
||||
A0_STATIC_INLINE
|
||||
void a0_tid_reset_atfork() {
|
||||
pthread_atfork(NULL, NULL, &a0_tid_reset);
|
||||
}
|
||||
|
||||
uint32_t a0_tid() {
|
||||
if (!a0_tid_cache) {
|
||||
a0_tid_cache = syscall(SYS_gettid);
|
||||
pthread_once(&a0_tid_reset_atfork_once, a0_tid_reset_atfork);
|
||||
}
|
||||
return a0_tid_cache;
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
#ifndef A0_TID_H
|
||||
#define A0_TID_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
uint32_t a0_tid();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // A0_TID_H
|
||||
@ -1,124 +0,0 @@
|
||||
#include "a0/empty.h"
|
||||
#include "a0/err.h"
|
||||
#include "a0/time.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "clock.h"
|
||||
#include "err_macro.h"
|
||||
#include "strconv.h"
|
||||
|
||||
const char A0_TIME_MONO[] = "a0_time_mono";
|
||||
|
||||
a0_err_t a0_time_mono_now(a0_time_mono_t* out) {
|
||||
return a0_clock_now(CLOCK_BOOTTIME, &out->ts);
|
||||
}
|
||||
|
||||
a0_err_t a0_time_mono_str(a0_time_mono_t time_mono, char mono_str[20]) {
|
||||
// Mono time as unsigned integer with up to 20 chars: "18446744072709551615"
|
||||
uint64_t ns = time_mono.ts.tv_sec * NS_PER_SEC + time_mono.ts.tv_nsec;
|
||||
mono_str[19] = '\0';
|
||||
return a0_u64_to_str(ns, mono_str, mono_str + 19, NULL);
|
||||
}
|
||||
|
||||
a0_err_t a0_time_mono_parse(const char mono_str[20], a0_time_mono_t* out) {
|
||||
uint64_t ns;
|
||||
A0_RETURN_ERR_ON_ERR(a0_str_to_u64(mono_str, mono_str + 19, &ns));
|
||||
out->ts.tv_sec = ns / NS_PER_SEC;
|
||||
out->ts.tv_nsec = ns % NS_PER_SEC;
|
||||
return A0_OK;
|
||||
}
|
||||
|
||||
a0_err_t a0_time_mono_add(a0_time_mono_t time_mono, int64_t add_nsec, a0_time_mono_t* out) {
|
||||
return a0_clock_add(time_mono.ts, add_nsec, &out->ts);
|
||||
}
|
||||
|
||||
const char A0_TIME_WALL[] = "a0_time_wall";
|
||||
|
||||
a0_err_t a0_time_wall_now(a0_time_wall_t* out) {
|
||||
A0_RETURN_SYSERR_ON_MINUS_ONE(clock_gettime(CLOCK_REALTIME, &out->ts));
|
||||
return A0_OK;
|
||||
}
|
||||
|
||||
a0_err_t a0_time_wall_str(a0_time_wall_t wall_time, char wall_str[36]) {
|
||||
// Wall time in RFC 3999 Nano: "2006-01-02T15:04:05.999999999-07:00"
|
||||
struct tm wall_tm;
|
||||
gmtime_r(&wall_time.ts.tv_sec, &wall_tm);
|
||||
|
||||
strftime(&wall_str[0], 20, "%Y-%m-%dT%H:%M:%S", &wall_tm);
|
||||
snprintf(&wall_str[19], 17, ".%09ld-00:00", wall_time.ts.tv_nsec);
|
||||
wall_str[35] = '\0';
|
||||
|
||||
return A0_OK;
|
||||
}
|
||||
|
||||
a0_err_t a0_time_wall_parse(const char wall_str[36], a0_time_wall_t* out) {
|
||||
// strptime requires _GNU_SOURCE, which we don't want, so we do it our selves.
|
||||
// Hard code "%Y-%m-%dT%H:%M:%S" + ".%09ld-00:00" pattern.
|
||||
|
||||
struct tm wall_tm = A0_EMPTY;
|
||||
// %Y
|
||||
A0_RETURN_ERR_ON_ERR(a0_str_to_u32(wall_str + 0, wall_str + 4, (uint32_t*)&wall_tm.tm_year));
|
||||
wall_tm.tm_year -= 1900;
|
||||
// -
|
||||
if (wall_str[4] != '-') {
|
||||
return A0_ERR_INVALID_ARG;
|
||||
}
|
||||
// %m
|
||||
A0_RETURN_ERR_ON_ERR(a0_str_to_u32(wall_str + 5, wall_str + 7, (uint32_t*)&wall_tm.tm_mon));
|
||||
if (wall_tm.tm_mon < 1 || wall_tm.tm_mon > 12) {
|
||||
return A0_ERR_INVALID_ARG;
|
||||
}
|
||||
wall_tm.tm_mon--;
|
||||
// -
|
||||
if (wall_str[7] != '-') {
|
||||
return A0_ERR_INVALID_ARG;
|
||||
}
|
||||
// %d
|
||||
A0_RETURN_ERR_ON_ERR(a0_str_to_u32(wall_str + 8, wall_str + 10, (uint32_t*)&wall_tm.tm_mday));
|
||||
if (wall_tm.tm_mday < 1 || wall_tm.tm_mday > 31) {
|
||||
return A0_ERR_INVALID_ARG;
|
||||
}
|
||||
// T
|
||||
if (wall_str[10] != 'T') {
|
||||
return A0_ERR_INVALID_ARG;
|
||||
}
|
||||
// %H
|
||||
A0_RETURN_ERR_ON_ERR(a0_str_to_u32(wall_str + 11, wall_str + 13, (uint32_t*)&wall_tm.tm_hour));
|
||||
if (wall_tm.tm_hour > 24) {
|
||||
return A0_ERR_INVALID_ARG;
|
||||
}
|
||||
// :
|
||||
if (wall_str[13] != ':') {
|
||||
return A0_ERR_INVALID_ARG;
|
||||
}
|
||||
// %M
|
||||
A0_RETURN_ERR_ON_ERR(a0_str_to_u32(wall_str + 14, wall_str + 16, (uint32_t*)&wall_tm.tm_min));
|
||||
if (wall_tm.tm_min > 60) {
|
||||
return A0_ERR_INVALID_ARG;
|
||||
}
|
||||
// :
|
||||
if (wall_str[16] != ':') {
|
||||
return A0_ERR_INVALID_ARG;
|
||||
}
|
||||
// %S
|
||||
A0_RETURN_ERR_ON_ERR(a0_str_to_u32(wall_str + 17, wall_str + 19, (uint32_t*)&wall_tm.tm_sec));
|
||||
if (wall_tm.tm_sec > 61) {
|
||||
return A0_ERR_INVALID_ARG;
|
||||
}
|
||||
// .
|
||||
if (wall_str[19] != '.') {
|
||||
return A0_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (memcmp(wall_str + 29, "-00:00", 6) != 0) {
|
||||
return A0_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Use timegm, cause it's a pain to compute months/years to seconds.
|
||||
out->ts.tv_sec = timegm(&wall_tm);
|
||||
return a0_str_to_u64(wall_str + 20, wall_str + 29, (uint64_t*)&out->ts.tv_nsec);
|
||||
}
|
||||
@ -1,97 +0,0 @@
|
||||
/**
|
||||
* \file time.h
|
||||
* \rst
|
||||
*
|
||||
* Mono Time
|
||||
* ---------
|
||||
*
|
||||
* | Mono time is a number of nanoseconds from machine boottime.
|
||||
* | This time cannot decrease and duration between ticks is constant.
|
||||
* | This time is not related to wall clock time.
|
||||
* | This time is most suitable for measuring durations.
|
||||
* |
|
||||
* | As a string, it is represented as a 20 char number:
|
||||
* | **18446744072709551615**
|
||||
* |
|
||||
* | Note that this uses CLOCK_BOOTTIME under the hood, not CLOCK_MONOTONIC.
|
||||
*
|
||||
* Wall Time
|
||||
* ---------
|
||||
*
|
||||
* | Wall time is an time object representing human-readable wall clock time.
|
||||
* | This time can decrease and duration between ticks is not constant.
|
||||
* | This time is most related to wall clock time.
|
||||
* | This time is not suitable for measuring durations.
|
||||
* |
|
||||
* | As a string, it is represented as a 36 char RFC 3999 Nano / ISO 8601:
|
||||
* | **2006-01-02T15:04:05.999999999-07:00**
|
||||
*
|
||||
* \endrst
|
||||
*/
|
||||
|
||||
#ifndef A0_TIME_H
|
||||
#define A0_TIME_H
|
||||
|
||||
#include "a0/err.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** \addtogroup TIME_MONO
|
||||
* @{
|
||||
*/
|
||||
|
||||
/// Header key for mono timestamps.
|
||||
extern const char A0_TIME_MONO[];
|
||||
|
||||
/// Monotonic timestamp. Despite the name, uses CLOCK_BOOTTIME.
|
||||
typedef struct a0_time_mono_s {
|
||||
struct timespec ts;
|
||||
} a0_time_mono_t;
|
||||
|
||||
/// Get the current mono timestamps.
|
||||
a0_err_t a0_time_mono_now(a0_time_mono_t*);
|
||||
|
||||
/// Stringify a given mono timestamps.
|
||||
a0_err_t a0_time_mono_str(a0_time_mono_t, char mono_str[20]);
|
||||
|
||||
/// Parse a stringified mono timestamps.
|
||||
a0_err_t a0_time_mono_parse(const char mono_str[20], a0_time_mono_t*);
|
||||
|
||||
/// Add a duration in nanoseconds to a mono timestamp.
|
||||
a0_err_t a0_time_mono_add(a0_time_mono_t, int64_t add_nsec, a0_time_mono_t*);
|
||||
|
||||
/** @}*/
|
||||
|
||||
/** \addtogroup TIME_WALL
|
||||
* @{
|
||||
*/
|
||||
|
||||
/// Header key for wall timestamps.
|
||||
extern const char A0_TIME_WALL[];
|
||||
|
||||
/// Wall clock timestamp.
|
||||
typedef struct a0_time_wall_s {
|
||||
struct timespec ts;
|
||||
} a0_time_wall_t;
|
||||
|
||||
/// Get the current wall timestamps.
|
||||
a0_err_t a0_time_wall_now(a0_time_wall_t*);
|
||||
|
||||
/// Stringify a given wall timestamps.
|
||||
a0_err_t a0_time_wall_str(a0_time_wall_t, char wall_str[36]);
|
||||
|
||||
/// Parse a stringified wall timestamps.
|
||||
a0_err_t a0_time_wall_parse(const char wall_str[36], a0_time_wall_t*);
|
||||
|
||||
/** @}*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // A0_TRANSPORT_H
|
||||
@ -1,7 +0,0 @@
|
||||
#ifndef A0_UNUSED_H
|
||||
#define A0_UNUSED_H
|
||||
|
||||
#define A0_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
|
||||
#define A0_MAYBE_UNUSED(X) ((void)(X))
|
||||
|
||||
#endif // A0_UNUSED_H
|
||||
@ -1,66 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "libipc/utility/log.h"
|
||||
#include "libipc/mutex.h"
|
||||
|
||||
#include "get_wait_time.h"
|
||||
#include "sync_obj_impl.h"
|
||||
|
||||
#include "a0/err_macro.h"
|
||||
#include "a0/mtx.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
namespace sync {
|
||||
|
||||
class condition : public sync::obj_impl<a0_cnd_t> {
|
||||
public:
|
||||
condition() = default;
|
||||
~condition() = default;
|
||||
|
||||
bool wait(ipc::sync::mutex &mtx, std::uint64_t tm) noexcept {
|
||||
if (!valid()) return false;
|
||||
if (tm == invalid_value) {
|
||||
int eno = A0_SYSERR(a0_cnd_wait(native(), static_cast<a0_mtx_t *>(mtx.native())));
|
||||
if (eno != 0) {
|
||||
ipc::error("fail condition wait[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
auto ts = detail::make_timespec(tm);
|
||||
int eno = A0_SYSERR(a0_cnd_timedwait(native(), static_cast<a0_mtx_t *>(mtx.native()), {ts}));
|
||||
if (eno != 0) {
|
||||
if (eno != ETIMEDOUT) {
|
||||
ipc::error("fail condition 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::sync::mutex &mtx) noexcept {
|
||||
if (!valid()) return false;
|
||||
int eno = A0_SYSERR(a0_cnd_signal(native(), static_cast<a0_mtx_t *>(mtx.native())));
|
||||
if (eno != 0) {
|
||||
ipc::error("fail condition notify[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool broadcast(ipc::sync::mutex &mtx) noexcept {
|
||||
if (!valid()) return false;
|
||||
int eno = A0_SYSERR(a0_cnd_broadcast(native(), static_cast<a0_mtx_t *>(mtx.native())));
|
||||
if (eno != 0) {
|
||||
ipc::error("fail condition broadcast[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace sync
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
@ -1,46 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cinttypes>
|
||||
#include <system_error>
|
||||
|
||||
#include "libipc/utility/log.h"
|
||||
|
||||
#include "a0/time.h"
|
||||
#include "a0/err_macro.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
inline bool calc_wait_time(timespec &ts, std::uint64_t tm /*ms*/) noexcept {
|
||||
std::int64_t add_ns = static_cast<std::int64_t>(tm * 1000000ull);
|
||||
if (add_ns < 0) {
|
||||
ipc::error("invalid time = " PRIu64 "\n", tm);
|
||||
return false;
|
||||
}
|
||||
a0_time_mono_t now;
|
||||
int eno = A0_SYSERR(a0_time_mono_now(&now));
|
||||
if (eno != 0) {
|
||||
ipc::error("fail get time[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
a0_time_mono_t *target = reinterpret_cast<a0_time_mono_t *>(&ts);
|
||||
if ((eno = A0_SYSERR(a0_time_mono_add(now, add_ns, target))) != 0) {
|
||||
ipc::error("fail get time[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline timespec make_timespec(std::uint64_t tm /*ms*/) noexcept(false) {
|
||||
timespec ts {};
|
||||
if (!calc_wait_time(ts, tm)) {
|
||||
ipc::error("fail calc_wait_time: tm = %zd, tv_sec = %ld, tv_nsec = %ld\n",
|
||||
tm, ts.tv_sec, ts.tv_nsec);
|
||||
throw std::system_error{static_cast<int>(errno), std::system_category()};
|
||||
}
|
||||
return ts;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
@ -1,204 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <system_error>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
|
||||
#include "libipc/platform/detail.h"
|
||||
#include "libipc/utility/log.h"
|
||||
#include "libipc/memory/resource.h"
|
||||
#include "libipc/shm.h"
|
||||
|
||||
#include "get_wait_time.h"
|
||||
#include "sync_obj_impl.h"
|
||||
|
||||
#include "a0/err_macro.h"
|
||||
#include "a0/mtx.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
namespace sync {
|
||||
|
||||
class robust_mutex : public sync::obj_impl<a0_mtx_t> {
|
||||
public:
|
||||
bool lock(std::uint64_t tm) noexcept {
|
||||
if (!valid()) return false;
|
||||
for (;;) {
|
||||
auto ts = detail::make_timespec(tm);
|
||||
int eno = A0_SYSERR(
|
||||
(tm == invalid_value) ? a0_mtx_lock(native())
|
||||
: a0_mtx_timedlock(native(), {ts}));
|
||||
switch (eno) {
|
||||
case 0:
|
||||
return true;
|
||||
case ETIMEDOUT:
|
||||
return false;
|
||||
case EOWNERDEAD: {
|
||||
int eno2 = A0_SYSERR(a0_mtx_consistent(native()));
|
||||
if (eno2 != 0) {
|
||||
ipc::error("fail mutex lock[%d] -> consistent[%d]\n", eno, eno2);
|
||||
return false;
|
||||
}
|
||||
int eno3 = A0_SYSERR(a0_mtx_unlock(native()));
|
||||
if (eno3 != 0) {
|
||||
ipc::error("fail mutex lock[%d] -> unlock[%d]\n", eno, eno3);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break; // loop again
|
||||
default:
|
||||
ipc::error("fail mutex lock[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool try_lock() noexcept(false) {
|
||||
if (!valid()) return false;
|
||||
int eno = A0_SYSERR(a0_mtx_timedlock(native(), {detail::make_timespec(0)}));
|
||||
switch (eno) {
|
||||
case 0:
|
||||
return true;
|
||||
case ETIMEDOUT:
|
||||
return false;
|
||||
case EOWNERDEAD: {
|
||||
int eno2 = A0_SYSERR(a0_mtx_consistent(native()));
|
||||
if (eno2 != 0) {
|
||||
ipc::error("fail mutex try_lock[%d] -> consistent[%d]\n", eno, eno2);
|
||||
break;
|
||||
}
|
||||
int eno3 = A0_SYSERR(a0_mtx_unlock(native()));
|
||||
if (eno3 != 0) {
|
||||
ipc::error("fail mutex try_lock[%d] -> unlock[%d]\n", eno, eno3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ipc::error("fail mutex try_lock[%d]\n", eno);
|
||||
break;
|
||||
}
|
||||
throw std::system_error{eno, std::system_category()};
|
||||
}
|
||||
|
||||
bool unlock() noexcept {
|
||||
if (!valid()) return false;
|
||||
int eno = A0_SYSERR(a0_mtx_unlock(native()));
|
||||
if (eno != 0) {
|
||||
ipc::error("fail mutex unlock[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class mutex {
|
||||
robust_mutex *mutex_ = nullptr;
|
||||
std::atomic<std::int32_t> *ref_ = nullptr;
|
||||
|
||||
struct curr_prog {
|
||||
struct shm_data {
|
||||
robust_mutex mtx;
|
||||
std::atomic<std::int32_t> ref;
|
||||
|
||||
struct init {
|
||||
char const *name;
|
||||
};
|
||||
shm_data(init arg)
|
||||
: mtx{}, ref{0} { mtx.open(arg.name); }
|
||||
};
|
||||
ipc::map<ipc::string, shm_data> mutex_handles;
|
||||
std::mutex lock;
|
||||
|
||||
static curr_prog &get() {
|
||||
static curr_prog info;
|
||||
return info;
|
||||
}
|
||||
};
|
||||
|
||||
void acquire_mutex(char const *name) {
|
||||
if (name == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto &info = curr_prog::get();
|
||||
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
|
||||
auto it = info.mutex_handles.find(name);
|
||||
if (it == info.mutex_handles.end()) {
|
||||
it = curr_prog::get().mutex_handles.emplace(name,
|
||||
curr_prog::shm_data::init{name}).first;
|
||||
}
|
||||
mutex_ = &it->second.mtx;
|
||||
ref_ = &it->second.ref;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void release_mutex(ipc::string const &name, F &&clear) {
|
||||
if (name.empty()) return;
|
||||
IPC_UNUSED_ std::lock_guard<std::mutex> guard {curr_prog::get().lock};
|
||||
auto it = curr_prog::get().mutex_handles.find(name);
|
||||
if (it == curr_prog::get().mutex_handles.end()) {
|
||||
return;
|
||||
}
|
||||
if (clear()) {
|
||||
curr_prog::get().mutex_handles.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
mutex() = default;
|
||||
~mutex() = default;
|
||||
|
||||
a0_mtx_t const *native() const noexcept {
|
||||
return valid() ? mutex_->native() : nullptr;
|
||||
}
|
||||
|
||||
a0_mtx_t *native() noexcept {
|
||||
return valid() ? mutex_->native() : nullptr;
|
||||
}
|
||||
|
||||
bool valid() const noexcept {
|
||||
return (mutex_ != nullptr) && (ref_ != nullptr) && mutex_->valid();
|
||||
}
|
||||
|
||||
bool open(char const *name) noexcept {
|
||||
close();
|
||||
acquire_mutex(name);
|
||||
if (!valid()) {
|
||||
return false;
|
||||
}
|
||||
ref_->fetch_add(1, std::memory_order_relaxed);
|
||||
return true;
|
||||
}
|
||||
|
||||
void close() noexcept {
|
||||
if ((mutex_ != nullptr) && (ref_ != nullptr)) {
|
||||
if (mutex_->name() != nullptr) {
|
||||
release_mutex(mutex_->name(), [this] {
|
||||
return ref_->fetch_sub(1, std::memory_order_relaxed) <= 1;
|
||||
});
|
||||
} else mutex_->close();
|
||||
}
|
||||
mutex_ = nullptr;
|
||||
ref_ = nullptr;
|
||||
}
|
||||
|
||||
bool lock(std::uint64_t tm) noexcept {
|
||||
if (!valid()) return false;
|
||||
return mutex_->lock(tm);
|
||||
}
|
||||
|
||||
bool try_lock() noexcept(false) {
|
||||
if (!valid()) return false;
|
||||
return mutex_->try_lock();
|
||||
}
|
||||
|
||||
bool unlock() noexcept {
|
||||
if (!valid()) return false;
|
||||
return mutex_->unlock();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace sync
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
@ -1,69 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "libipc/utility/log.h"
|
||||
#include "libipc/shm.h"
|
||||
|
||||
#include "a0/empty.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
namespace sync {
|
||||
|
||||
template <typename SyncT>
|
||||
class obj_impl {
|
||||
public:
|
||||
using sync_t = SyncT;
|
||||
|
||||
protected:
|
||||
ipc::shm::handle shm_;
|
||||
sync_t *h_ = nullptr;
|
||||
|
||||
sync_t *acquire_handle(char const *name) {
|
||||
if (!shm_.acquire(name, sizeof(sync_t))) {
|
||||
ipc::error("[acquire_handle] fail shm.acquire: %s\n", name);
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<sync_t *>(shm_.get());
|
||||
}
|
||||
|
||||
public:
|
||||
obj_impl() = default;
|
||||
~obj_impl() = default;
|
||||
|
||||
sync_t const *native() const noexcept {
|
||||
return h_;
|
||||
}
|
||||
|
||||
sync_t *native() noexcept {
|
||||
return h_;
|
||||
}
|
||||
|
||||
char const *name() const noexcept {
|
||||
return shm_.name();
|
||||
}
|
||||
|
||||
bool valid() const noexcept {
|
||||
return h_ != nullptr;
|
||||
}
|
||||
|
||||
bool open(char const *name) noexcept {
|
||||
close();
|
||||
if ((h_ = acquire_handle(name)) == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (shm_.ref() > 1) {
|
||||
return true;
|
||||
}
|
||||
*h_ = A0_EMPTY;
|
||||
return true;
|
||||
}
|
||||
|
||||
void close() noexcept {
|
||||
shm_.release();
|
||||
h_ = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace sync
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
@ -1,13 +0,0 @@
|
||||
|
||||
#include "libipc/platform/detail.h"
|
||||
#if defined(IPC_OS_WINDOWS_)
|
||||
#elif defined(IPC_OS_LINUX_)
|
||||
#include "libipc/platform/linux/a0/err.c"
|
||||
#include "libipc/platform/linux/a0/mtx.c"
|
||||
#include "libipc/platform/linux/a0/strconv.c"
|
||||
#include "libipc/platform/linux/a0/tid.c"
|
||||
#include "libipc/platform/linux/a0/time.c"
|
||||
#elif defined(IPC_OS_QNX_)
|
||||
#else/*IPC_OS*/
|
||||
# error "Unsupported platform."
|
||||
#endif
|
||||
@ -1,9 +0,0 @@
|
||||
|
||||
#include "libipc/platform/detail.h"
|
||||
#if defined(IPC_OS_WINDOWS_)
|
||||
#include "libipc/platform/win/shm_win.cpp"
|
||||
#elif defined(IPC_OS_LINUX_) || defined(IPC_OS_QNX_)
|
||||
#include "libipc/platform/posix/shm_posix.cpp"
|
||||
#else/*IPC_OS*/
|
||||
# error "Unsupported platform."
|
||||
#endif
|
||||
@ -1,141 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "libipc/utility/log.h"
|
||||
#include "libipc/utility/scope_guard.h"
|
||||
#include "libipc/mutex.h"
|
||||
#include "libipc/shm.h"
|
||||
|
||||
#include "get_wait_time.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
namespace sync {
|
||||
|
||||
class condition {
|
||||
ipc::shm::handle shm_;
|
||||
pthread_cond_t *cond_ = nullptr;
|
||||
|
||||
pthread_cond_t *acquire_cond(char const *name) {
|
||||
if (!shm_.acquire(name, sizeof(pthread_cond_t))) {
|
||||
ipc::error("[acquire_cond] fail shm.acquire: %s\n", name);
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<pthread_cond_t *>(shm_.get());
|
||||
}
|
||||
|
||||
public:
|
||||
condition() = default;
|
||||
~condition() = default;
|
||||
|
||||
pthread_cond_t const *native() const noexcept {
|
||||
return cond_;
|
||||
}
|
||||
|
||||
pthread_cond_t *native() noexcept {
|
||||
return cond_;
|
||||
}
|
||||
|
||||
bool valid() const noexcept {
|
||||
static const char tmp[sizeof(pthread_cond_t)] {};
|
||||
return (cond_ != nullptr)
|
||||
&& (std::memcmp(tmp, cond_, sizeof(pthread_cond_t)) != 0);
|
||||
}
|
||||
|
||||
bool open(char const *name) noexcept {
|
||||
close();
|
||||
if ((cond_ = acquire_cond(name)) == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (shm_.ref() > 1) {
|
||||
return valid();
|
||||
}
|
||||
::pthread_cond_destroy(cond_);
|
||||
auto finally = ipc::guard([this] { close(); }); // close when failed
|
||||
// init condition
|
||||
int eno;
|
||||
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 = guard([&cond_attr] { ::pthread_condattr_destroy(&cond_attr); });
|
||||
if ((eno = ::pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED)) != 0) {
|
||||
ipc::error("fail pthread_condattr_setpshared[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
*cond_ = PTHREAD_COND_INITIALIZER;
|
||||
if ((eno = ::pthread_cond_init(cond_, &cond_attr)) != 0) {
|
||||
ipc::error("fail pthread_cond_init[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
finally.dismiss();
|
||||
return valid();
|
||||
}
|
||||
|
||||
void close() noexcept {
|
||||
if ((shm_.ref() <= 1) && cond_ != nullptr) {
|
||||
int eno;
|
||||
if ((eno = ::pthread_cond_destroy(cond_)) != 0) {
|
||||
ipc::error("fail pthread_cond_destroy[%d]\n", eno);
|
||||
}
|
||||
}
|
||||
shm_.release();
|
||||
cond_ = nullptr;
|
||||
}
|
||||
|
||||
bool wait(ipc::sync::mutex &mtx, std::uint64_t tm) noexcept {
|
||||
if (!valid()) return false;
|
||||
switch (tm) {
|
||||
case invalid_value: {
|
||||
int eno;
|
||||
if ((eno = ::pthread_cond_wait(cond_, static_cast<pthread_mutex_t *>(mtx.native()))) != 0) {
|
||||
ipc::error("fail pthread_cond_wait[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
auto ts = detail::make_timespec(tm);
|
||||
int eno;
|
||||
if ((eno = ::pthread_cond_timedwait(cond_, static_cast<pthread_mutex_t *>(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;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool notify(ipc::sync::mutex &) noexcept {
|
||||
if (!valid()) return false;
|
||||
int eno;
|
||||
if ((eno = ::pthread_cond_signal(cond_)) != 0) {
|
||||
ipc::error("fail pthread_cond_signal[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool broadcast(ipc::sync::mutex &) noexcept {
|
||||
if (!valid()) return false;
|
||||
int eno;
|
||||
if ((eno = ::pthread_cond_broadcast(cond_)) != 0) {
|
||||
ipc::error("fail pthread_cond_broadcast[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace sync
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
@ -1,14 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
inline char const *curr_error() noexcept {
|
||||
return ::strerror(errno);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <system_error>
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "libipc/utility/log.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
inline bool calc_wait_time(timespec &ts, std::uint64_t tm /*ms*/) noexcept {
|
||||
timeval now;
|
||||
int eno = ::gettimeofday(&now, NULL);
|
||||
if (eno != 0) {
|
||||
ipc::error("fail gettimeofday [%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
ts.tv_nsec = (now.tv_usec + (tm % 1000) * 1000) * 1000;
|
||||
ts.tv_sec = now.tv_sec + (tm / 1000) + (ts.tv_nsec / 1000000000l);
|
||||
ts.tv_nsec %= 1000000000l;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline timespec make_timespec(std::uint64_t tm /*ms*/) noexcept(false) {
|
||||
timespec ts {};
|
||||
if (!calc_wait_time(ts, tm)) {
|
||||
ipc::error("fail calc_wait_time: tm = %zd, tv_sec = %ld, tv_nsec = %ld\n",
|
||||
tm, ts.tv_sec, ts.tv_nsec);
|
||||
throw std::system_error{static_cast<int>(errno), std::system_category()};
|
||||
}
|
||||
return ts;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
@ -1,237 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <system_error>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "libipc/platform/detail.h"
|
||||
#include "libipc/utility/log.h"
|
||||
#include "libipc/utility/scope_guard.h"
|
||||
#include "libipc/memory/resource.h"
|
||||
#include "libipc/shm.h"
|
||||
|
||||
#include "get_wait_time.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
namespace sync {
|
||||
|
||||
class mutex {
|
||||
ipc::shm::handle *shm_ = nullptr;
|
||||
std::atomic<std::int32_t> *ref_ = nullptr;
|
||||
pthread_mutex_t *mutex_ = nullptr;
|
||||
|
||||
struct curr_prog {
|
||||
struct shm_data {
|
||||
ipc::shm::handle shm;
|
||||
std::atomic<std::int32_t> ref;
|
||||
|
||||
struct init {
|
||||
char const *name;
|
||||
std::size_t size;
|
||||
};
|
||||
shm_data(init arg)
|
||||
: shm{arg.name, arg.size}, ref{0} {}
|
||||
};
|
||||
ipc::map<ipc::string, shm_data> mutex_handles;
|
||||
std::mutex lock;
|
||||
|
||||
static curr_prog &get() {
|
||||
static curr_prog info;
|
||||
return info;
|
||||
}
|
||||
};
|
||||
|
||||
pthread_mutex_t *acquire_mutex(char const *name) {
|
||||
if (name == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
auto &info = curr_prog::get();
|
||||
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
|
||||
auto it = info.mutex_handles.find(name);
|
||||
if (it == info.mutex_handles.end()) {
|
||||
it = curr_prog::get().mutex_handles.emplace(name,
|
||||
curr_prog::shm_data::init{name, sizeof(pthread_mutex_t)}).first;
|
||||
}
|
||||
shm_ = &it->second.shm;
|
||||
ref_ = &it->second.ref;
|
||||
if (shm_ == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<pthread_mutex_t *>(shm_->get());
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void release_mutex(ipc::string const &name, F &&clear) {
|
||||
if (name.empty()) return;
|
||||
IPC_UNUSED_ std::lock_guard<std::mutex> guard {curr_prog::get().lock};
|
||||
auto it = curr_prog::get().mutex_handles.find(name);
|
||||
if (it == curr_prog::get().mutex_handles.end()) {
|
||||
return;
|
||||
}
|
||||
if (clear()) {
|
||||
curr_prog::get().mutex_handles.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
mutex() = default;
|
||||
~mutex() = default;
|
||||
|
||||
pthread_mutex_t const *native() const noexcept {
|
||||
return mutex_;
|
||||
}
|
||||
|
||||
pthread_mutex_t *native() noexcept {
|
||||
return mutex_;
|
||||
}
|
||||
|
||||
bool valid() const noexcept {
|
||||
static const char tmp[sizeof(pthread_mutex_t)] {};
|
||||
return (shm_ != nullptr) && (ref_ != nullptr) && (mutex_ != nullptr)
|
||||
&& (std::memcmp(tmp, mutex_, sizeof(pthread_mutex_t)) != 0);
|
||||
}
|
||||
|
||||
bool open(char const *name) noexcept {
|
||||
close();
|
||||
if ((mutex_ = acquire_mutex(name)) == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto self_ref = ref_->fetch_add(1, std::memory_order_relaxed);
|
||||
if (shm_->ref() > 1 || self_ref > 0) {
|
||||
return valid();
|
||||
}
|
||||
::pthread_mutex_destroy(mutex_);
|
||||
auto finally = ipc::guard([this] { close(); }); // close when failed
|
||||
// init mutex
|
||||
int eno;
|
||||
pthread_mutexattr_t mutex_attr;
|
||||
if ((eno = ::pthread_mutexattr_init(&mutex_attr)) != 0) {
|
||||
ipc::error("fail pthread_mutexattr_init[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
IPC_UNUSED_ auto guard_mutex_attr = guard([&mutex_attr] { ::pthread_mutexattr_destroy(&mutex_attr); });
|
||||
if ((eno = ::pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED)) != 0) {
|
||||
ipc::error("fail pthread_mutexattr_setpshared[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
if ((eno = ::pthread_mutexattr_setrobust(&mutex_attr, PTHREAD_MUTEX_ROBUST)) != 0) {
|
||||
ipc::error("fail pthread_mutexattr_setrobust[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
*mutex_ = PTHREAD_MUTEX_INITIALIZER;
|
||||
if ((eno = ::pthread_mutex_init(mutex_, &mutex_attr)) != 0) {
|
||||
ipc::error("fail pthread_mutex_init[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
finally.dismiss();
|
||||
return valid();
|
||||
}
|
||||
|
||||
void close() noexcept {
|
||||
if ((ref_ != nullptr) && (shm_ != nullptr) && (mutex_ != nullptr)) {
|
||||
if (shm_->name() != nullptr) {
|
||||
release_mutex(shm_->name(), [this] {
|
||||
auto self_ref = ref_->fetch_sub(1, std::memory_order_relaxed);
|
||||
if ((shm_->ref() <= 1) && (self_ref <= 1)) {
|
||||
int eno;
|
||||
if ((eno = ::pthread_mutex_destroy(mutex_)) != 0) {
|
||||
ipc::error("fail pthread_mutex_destroy[%d]\n", eno);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
} else shm_->release();
|
||||
}
|
||||
shm_ = nullptr;
|
||||
ref_ = nullptr;
|
||||
mutex_ = nullptr;
|
||||
}
|
||||
|
||||
bool lock(std::uint64_t tm) noexcept {
|
||||
if (!valid()) return false;
|
||||
for (;;) {
|
||||
auto ts = detail::make_timespec(tm);
|
||||
int eno = (tm == invalid_value)
|
||||
? ::pthread_mutex_lock(mutex_)
|
||||
: ::pthread_mutex_timedlock(mutex_, &ts);
|
||||
switch (eno) {
|
||||
case 0:
|
||||
return true;
|
||||
case ETIMEDOUT:
|
||||
return false;
|
||||
case EOWNERDEAD: {
|
||||
if (shm_->ref() > 1) {
|
||||
shm_->sub_ref();
|
||||
}
|
||||
int eno2 = ::pthread_mutex_consistent(mutex_);
|
||||
if (eno2 != 0) {
|
||||
ipc::error("fail pthread_mutex_lock[%d], pthread_mutex_consistent[%d]\n", eno, eno2);
|
||||
return false;
|
||||
}
|
||||
int eno3 = ::pthread_mutex_unlock(mutex_);
|
||||
if (eno3 != 0) {
|
||||
ipc::error("fail pthread_mutex_lock[%d], pthread_mutex_unlock[%d]\n", eno, eno3);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break; // loop again
|
||||
default:
|
||||
ipc::error("fail pthread_mutex_lock[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool try_lock() noexcept(false) {
|
||||
if (!valid()) return false;
|
||||
auto ts = detail::make_timespec(0);
|
||||
int eno = ::pthread_mutex_timedlock(mutex_, &ts);
|
||||
switch (eno) {
|
||||
case 0:
|
||||
return true;
|
||||
case ETIMEDOUT:
|
||||
return false;
|
||||
case EOWNERDEAD: {
|
||||
if (shm_->ref() > 1) {
|
||||
shm_->sub_ref();
|
||||
}
|
||||
int eno2 = ::pthread_mutex_consistent(mutex_);
|
||||
if (eno2 != 0) {
|
||||
ipc::error("fail pthread_mutex_timedlock[%d], pthread_mutex_consistent[%d]\n", eno, eno2);
|
||||
break;
|
||||
}
|
||||
int eno3 = ::pthread_mutex_unlock(mutex_);
|
||||
if (eno3 != 0) {
|
||||
ipc::error("fail pthread_mutex_timedlock[%d], pthread_mutex_unlock[%d]\n", eno, eno3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ipc::error("fail pthread_mutex_timedlock[%d]\n", eno);
|
||||
break;
|
||||
}
|
||||
throw std::system_error{eno, std::system_category()};
|
||||
}
|
||||
|
||||
bool unlock() noexcept {
|
||||
if (!valid()) return false;
|
||||
int eno;
|
||||
if ((eno = ::pthread_mutex_unlock(mutex_)) != 0) {
|
||||
ipc::error("fail pthread_mutex_unlock[%d]\n", eno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace sync
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
@ -1,100 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <fcntl.h> /* For O_* constants */
|
||||
#include <sys/stat.h> /* For mode constants */
|
||||
#include <semaphore.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "libipc/utility/log.h"
|
||||
#include "libipc/shm.h"
|
||||
|
||||
#include "get_wait_time.h"
|
||||
#include "get_error.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
namespace sync {
|
||||
|
||||
class semaphore {
|
||||
ipc::shm::handle shm_;
|
||||
sem_t *h_ = SEM_FAILED;
|
||||
|
||||
public:
|
||||
semaphore() = default;
|
||||
~semaphore() noexcept = default;
|
||||
|
||||
sem_t *native() const noexcept {
|
||||
return h_;
|
||||
}
|
||||
|
||||
bool valid() const noexcept {
|
||||
return h_ != SEM_FAILED;
|
||||
}
|
||||
|
||||
bool open(char const *name, std::uint32_t count) noexcept {
|
||||
close();
|
||||
if (!shm_.acquire(name, 1)) {
|
||||
ipc::error("[open_semaphore] fail shm.acquire: %s\n", name);
|
||||
return false;
|
||||
}
|
||||
h_ = ::sem_open(name, O_CREAT, 0666, static_cast<unsigned>(count));
|
||||
if (h_ == SEM_FAILED) {
|
||||
ipc::error("fail sem_open[%s]: name = %s\n", curr_error(), name);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void close() noexcept {
|
||||
if (!valid()) return;
|
||||
if (::sem_close(h_) != 0) {
|
||||
ipc::error("fail sem_close[%s]\n", curr_error());
|
||||
}
|
||||
h_ = SEM_FAILED;
|
||||
if (shm_.name() != nullptr) {
|
||||
std::string name = shm_.name();
|
||||
if (shm_.release() <= 1) {
|
||||
if (::sem_unlink(name.c_str()) != 0) {
|
||||
ipc::error("fail sem_unlink[%s]: name = %s\n", curr_error(), name.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool wait(std::uint64_t tm) noexcept {
|
||||
if (!valid()) return false;
|
||||
if (tm == invalid_value) {
|
||||
if (::sem_wait(h_) != 0) {
|
||||
ipc::error("fail sem_wait[%s]\n", curr_error());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
auto ts = detail::make_timespec(tm);
|
||||
if (::sem_timedwait(h_, &ts) != 0) {
|
||||
if (errno != ETIMEDOUT) {
|
||||
ipc::error("fail sem_timedwait[%s]: tm = %zd, tv_sec = %ld, tv_nsec = %ld\n",
|
||||
curr_error(), tm, ts.tv_sec, ts.tv_nsec);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool post(std::uint32_t count) noexcept {
|
||||
if (!valid()) return false;
|
||||
for (std::uint32_t i = 0; i < count; ++i) {
|
||||
if (::sem_post(h_) != 0) {
|
||||
ipc::error("fail sem_post[%s]\n", curr_error());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace sync
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
@ -1,197 +0,0 @@
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <cstring>
|
||||
|
||||
#include "libipc/shm.h"
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/pool_alloc.h"
|
||||
|
||||
#include "libipc/utility/log.h"
|
||||
#include "libipc/memory/resource.h"
|
||||
|
||||
#include "get_error.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct info_t {
|
||||
std::atomic<std::int32_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[%s]: name = %s\n", curr_error(), name);
|
||||
return nullptr;
|
||||
}
|
||||
auto ii = mem::alloc<id_info_t>();
|
||||
ii->fd_ = fd;
|
||||
ii->size_ = size;
|
||||
ii->name_ = std::move(op_name);
|
||||
return ii;
|
||||
}
|
||||
|
||||
std::int32_t get_ref(id_t id) {
|
||||
if (id == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
auto ii = static_cast<id_info_t*>(id);
|
||||
if (ii->mem_ == nullptr || ii->size_ == 0) {
|
||||
return 0;
|
||||
}
|
||||
return acc_of(ii->mem_, ii->size_).load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void sub_ref(id_t id) {
|
||||
if (id == nullptr) {
|
||||
ipc::error("fail sub_ref: invalid id (null)\n");
|
||||
return;
|
||||
}
|
||||
auto ii = static_cast<id_info_t*>(id);
|
||||
if (ii->mem_ == nullptr || ii->size_ == 0) {
|
||||
ipc::error("fail sub_ref: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
|
||||
return;
|
||||
}
|
||||
acc_of(ii->mem_, ii->size_).fetch_sub(1, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
void * get_mem(id_t id, std::size_t * size) {
|
||||
if (id == nullptr) {
|
||||
ipc::error("fail get_mem: invalid id (null)\n");
|
||||
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 get_mem: invalid id (fd = -1)\n");
|
||||
return nullptr;
|
||||
}
|
||||
if (ii->size_ == 0) {
|
||||
struct stat st;
|
||||
if (::fstat(fd, &st) != 0) {
|
||||
ipc::error("fail fstat[%s]: name = %s, size = %zd\n", curr_error(), 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 get_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[%s]: name = %s, size = %zd\n", curr_error(), 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[%s]: name = %s, size = %zd\n", curr_error(), 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;
|
||||
}
|
||||
|
||||
std::int32_t release(id_t id) {
|
||||
if (id == nullptr) {
|
||||
ipc::error("fail release: invalid id (null)\n");
|
||||
return -1;
|
||||
}
|
||||
std::int32_t ret = -1;
|
||||
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 ((ret = acc_of(ii->mem_, ii->size_).fetch_sub(1, std::memory_order_acq_rel)) <= 1) {
|
||||
::munmap(ii->mem_, ii->size_);
|
||||
if (!ii->name_.empty()) {
|
||||
::shm_unlink(ii->name_.c_str());
|
||||
}
|
||||
}
|
||||
else ::munmap(ii->mem_, ii->size_);
|
||||
mem::free(ii);
|
||||
return ret;
|
||||
}
|
||||
|
||||
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,119 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include "libipc/utility/log.h"
|
||||
#include "libipc/utility/scope_guard.h"
|
||||
#include "libipc/platform/detail.h"
|
||||
#include "libipc/mutex.h"
|
||||
#include "libipc/semaphore.h"
|
||||
#include "libipc/shm.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
namespace sync {
|
||||
|
||||
class condition {
|
||||
ipc::sync::semaphore sem_;
|
||||
ipc::sync::mutex lock_;
|
||||
ipc::shm::handle shm_;
|
||||
|
||||
std::int32_t &counter() {
|
||||
return *static_cast<std::int32_t *>(shm_.get());
|
||||
}
|
||||
|
||||
public:
|
||||
condition() = default;
|
||||
~condition() noexcept = default;
|
||||
|
||||
auto native() noexcept {
|
||||
return sem_.native();
|
||||
}
|
||||
|
||||
auto native() const noexcept {
|
||||
return sem_.native();
|
||||
}
|
||||
|
||||
bool valid() const noexcept {
|
||||
return sem_.valid() && lock_.valid() && shm_.valid();
|
||||
}
|
||||
|
||||
bool open(char const *name) noexcept {
|
||||
close();
|
||||
if (!sem_.open((std::string{"_cond_sem_"} + name).c_str())) {
|
||||
return false;
|
||||
}
|
||||
auto finally_sem = ipc::guard([this] { sem_.close(); }); // close when failed
|
||||
if (!lock_.open((std::string{"_cond_lock_"} + name).c_str())) {
|
||||
return false;
|
||||
}
|
||||
auto finally_lock = ipc::guard([this] { lock_.close(); }); // close when failed
|
||||
if (!shm_.acquire((std::string{"_cond_shm_"} + name).c_str(), sizeof(std::int32_t))) {
|
||||
return false;
|
||||
}
|
||||
finally_lock.dismiss();
|
||||
finally_sem.dismiss();
|
||||
return valid();
|
||||
}
|
||||
|
||||
void close() noexcept {
|
||||
if (!valid()) return;
|
||||
sem_.close();
|
||||
lock_.close();
|
||||
shm_.release();
|
||||
}
|
||||
|
||||
bool wait(ipc::sync::mutex &mtx, std::uint64_t tm) noexcept {
|
||||
if (!valid()) return false;
|
||||
auto &cnt = counter();
|
||||
{
|
||||
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> guard {lock_};
|
||||
cnt = (cnt < 0) ? 1 : cnt + 1;
|
||||
}
|
||||
DWORD ms = (tm == invalid_value) ? INFINITE : static_cast<DWORD>(tm);
|
||||
/**
|
||||
* @see
|
||||
* - https://www.microsoft.com/en-us/research/wp-content/uploads/2004/12/ImplementingCVs.pdf
|
||||
* - https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-signalobjectandwait
|
||||
*/
|
||||
bool rs = ::SignalObjectAndWait(mtx.native(), sem_.native(), ms, FALSE) == WAIT_OBJECT_0;
|
||||
bool rl = mtx.lock(); // INFINITE
|
||||
if (!rs) {
|
||||
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> guard {lock_};
|
||||
cnt -= 1;
|
||||
}
|
||||
return rs && rl;
|
||||
}
|
||||
|
||||
bool notify(ipc::sync::mutex &) noexcept {
|
||||
if (!valid()) return false;
|
||||
auto &cnt = counter();
|
||||
if (!lock_.lock()) return false;
|
||||
bool ret = false;
|
||||
if (cnt > 0) {
|
||||
ret = sem_.post(1);
|
||||
cnt -= 1;
|
||||
}
|
||||
return lock_.unlock() && ret;
|
||||
}
|
||||
|
||||
bool broadcast(ipc::sync::mutex &) noexcept {
|
||||
if (!valid()) return false;
|
||||
auto &cnt = counter();
|
||||
if (!lock_.lock()) return false;
|
||||
bool ret = false;
|
||||
if (cnt > 0) {
|
||||
ret = sem_.post(cnt);
|
||||
cnt = 0;
|
||||
}
|
||||
return lock_.unlock() && ret;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace sync
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
@ -1,35 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <securitybaseapi.h>
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
inline LPSECURITY_ATTRIBUTES get_sa() {
|
||||
static struct initiator {
|
||||
|
||||
SECURITY_DESCRIPTOR sd_;
|
||||
SECURITY_ATTRIBUTES sa_;
|
||||
|
||||
bool succ_ = false;
|
||||
|
||||
initiator() {
|
||||
if (!::InitializeSecurityDescriptor(&sd_, SECURITY_DESCRIPTOR_REVISION)) {
|
||||
ipc::error("fail InitializeSecurityDescriptor[%d]\n", static_cast<int>(::GetLastError()));
|
||||
return;
|
||||
}
|
||||
if (!::SetSecurityDescriptorDacl(&sd_, TRUE, NULL, FALSE)) {
|
||||
ipc::error("fail SetSecurityDescriptorDacl[%d]\n", static_cast<int>(::GetLastError()));
|
||||
return;
|
||||
}
|
||||
sa_.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
sa_.bInheritHandle = FALSE;
|
||||
sa_.lpSecurityDescriptor = &sd_;
|
||||
succ_ = true;
|
||||
}
|
||||
} handle;
|
||||
return handle.succ_ ? &handle.sa_ : nullptr;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
@ -1,96 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <system_error>
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include "libipc/utility/log.h"
|
||||
|
||||
#include "to_tchar.h"
|
||||
#include "get_sa.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
namespace sync {
|
||||
|
||||
class mutex {
|
||||
HANDLE h_ = NULL;
|
||||
|
||||
public:
|
||||
mutex() noexcept = default;
|
||||
~mutex() noexcept = default;
|
||||
|
||||
HANDLE native() const noexcept {
|
||||
return h_;
|
||||
}
|
||||
|
||||
bool valid() const noexcept {
|
||||
return h_ != NULL;
|
||||
}
|
||||
|
||||
bool open(char const *name) noexcept {
|
||||
close();
|
||||
h_ = ::CreateMutex(detail::get_sa(), FALSE, ipc::detail::to_tchar(name).c_str());
|
||||
if (h_ == NULL) {
|
||||
ipc::error("fail CreateMutex[%lu]: %s\n", ::GetLastError(), name);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void close() noexcept {
|
||||
if (!valid()) return;
|
||||
::CloseHandle(h_);
|
||||
h_ = NULL;
|
||||
}
|
||||
|
||||
bool lock(std::uint64_t tm) noexcept {
|
||||
DWORD ret, ms = (tm == invalid_value) ? INFINITE : static_cast<DWORD>(tm);
|
||||
for(;;) {
|
||||
switch ((ret = ::WaitForSingleObject(h_, ms))) {
|
||||
case WAIT_OBJECT_0:
|
||||
return true;
|
||||
case WAIT_TIMEOUT:
|
||||
return false;
|
||||
case WAIT_ABANDONED:
|
||||
ipc::log("fail WaitForSingleObject[%lu]: WAIT_ABANDONED, try again.\n", ::GetLastError());
|
||||
if (!unlock()) {
|
||||
return false;
|
||||
}
|
||||
break; // loop again
|
||||
default:
|
||||
ipc::error("fail WaitForSingleObject[%lu]: 0x%08X\n", ::GetLastError(), ret);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool try_lock() noexcept(false) {
|
||||
DWORD ret = ::WaitForSingleObject(h_, 0);
|
||||
switch (ret) {
|
||||
case WAIT_OBJECT_0:
|
||||
return true;
|
||||
case WAIT_TIMEOUT:
|
||||
return false;
|
||||
case WAIT_ABANDONED:
|
||||
unlock();
|
||||
IPC_FALLTHROUGH_;
|
||||
default:
|
||||
ipc::error("fail WaitForSingleObject[%lu]: 0x%08X\n", ::GetLastError(), ret);
|
||||
throw std::system_error{static_cast<int>(ret), std::system_category()};
|
||||
}
|
||||
}
|
||||
|
||||
bool unlock() noexcept {
|
||||
if (!::ReleaseMutex(h_)) {
|
||||
ipc::error("fail ReleaseMutex[%lu]\n", ::GetLastError());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace sync
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
@ -1,74 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include "libipc/utility/log.h"
|
||||
|
||||
#include "to_tchar.h"
|
||||
#include "get_sa.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
namespace sync {
|
||||
|
||||
class semaphore {
|
||||
HANDLE h_ = NULL;
|
||||
|
||||
public:
|
||||
semaphore() noexcept = default;
|
||||
~semaphore() noexcept = default;
|
||||
|
||||
HANDLE native() const noexcept {
|
||||
return h_;
|
||||
}
|
||||
|
||||
bool valid() const noexcept {
|
||||
return h_ != NULL;
|
||||
}
|
||||
|
||||
bool open(char const *name, std::uint32_t count) noexcept {
|
||||
close();
|
||||
h_ = ::CreateSemaphore(detail::get_sa(),
|
||||
static_cast<LONG>(count), LONG_MAX,
|
||||
ipc::detail::to_tchar(name).c_str());
|
||||
if (h_ == NULL) {
|
||||
ipc::error("fail CreateSemaphore[%lu]: %s\n", ::GetLastError(), name);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void close() noexcept {
|
||||
if (!valid()) return;
|
||||
::CloseHandle(h_);
|
||||
h_ = NULL;
|
||||
}
|
||||
|
||||
bool wait(std::uint64_t tm) noexcept {
|
||||
DWORD ret, ms = (tm == invalid_value) ? INFINITE : static_cast<DWORD>(tm);
|
||||
switch ((ret = ::WaitForSingleObject(h_, ms))) {
|
||||
case WAIT_OBJECT_0:
|
||||
return true;
|
||||
case WAIT_TIMEOUT:
|
||||
return false;
|
||||
case WAIT_ABANDONED:
|
||||
default:
|
||||
ipc::error("fail WaitForSingleObject[%lu]: 0x%08X\n", ::GetLastError(), ret);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool post(std::uint32_t count) noexcept {
|
||||
if (!::ReleaseSemaphore(h_, static_cast<LONG>(count), NULL)) {
|
||||
ipc::error("fail ReleaseSemaphore[%lu]\n", ::GetLastError());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace sync
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
@ -1,135 +0,0 @@
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "libipc/shm.h"
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/pool_alloc.h"
|
||||
|
||||
#include "libipc/utility/log.h"
|
||||
#include "libipc/memory/resource.h"
|
||||
|
||||
#include "to_tchar.h"
|
||||
#include "get_sa.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, detail::get_sa(), 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;
|
||||
}
|
||||
|
||||
std::int32_t get_ref(id_t) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sub_ref(id_t) {
|
||||
// Do Nothing.
|
||||
}
|
||||
|
||||
void * get_mem(id_t id, std::size_t * size) {
|
||||
if (id == nullptr) {
|
||||
ipc::error("fail get_mem: invalid id (null)\n");
|
||||
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);
|
||||
}
|
||||
|
||||
std::int32_t release(id_t id) {
|
||||
if (id == nullptr) {
|
||||
ipc::error("fail release: invalid id (null)\n");
|
||||
return -1;
|
||||
}
|
||||
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);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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,74 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <type_traits>
|
||||
#include <string>
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <cstddef>
|
||||
|
||||
#include "libipc/utility/concept.h"
|
||||
#include "libipc/memory/resource.h"
|
||||
#include "libipc/platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace 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); // noconv
|
||||
}
|
||||
|
||||
/**
|
||||
* @remarks codecvt_utf8_utf16/std::wstring_convert is deprecated
|
||||
* @see https://codingtidbit.com/2020/02/09/c17-codecvt_utf8-is-deprecated/
|
||||
* https://stackoverflow.com/questions/42946335/deprecated-header-codecvt-replacement
|
||||
* https://en.cppreference.com/w/cpp/locale/codecvt/in
|
||||
* https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar
|
||||
*/
|
||||
template <typename T = TCHAR>
|
||||
auto to_tchar(ipc::string &&external) -> IsSameChar<T, ipc::wstring> {
|
||||
if (external.empty()) {
|
||||
return {}; // noconv
|
||||
}
|
||||
/**
|
||||
* CP_ACP : The system default Windows ANSI code page.
|
||||
* CP_MACCP : The current system Macintosh code page.
|
||||
* CP_OEMCP : The current system OEM code page.
|
||||
* CP_SYMBOL : Symbol code page (42).
|
||||
* CP_THREAD_ACP: The Windows ANSI code page for the current thread.
|
||||
* CP_UTF7 : UTF-7. Use this value only when forced by a 7-bit transport mechanism. Use of UTF-8 is preferred.
|
||||
* CP_UTF8 : UTF-8.
|
||||
*/
|
||||
int size_needed = ::MultiByteToWideChar(CP_UTF8, 0, &external[0], (int)external.size(), NULL, 0);
|
||||
if (size_needed <= 0) {
|
||||
return {};
|
||||
}
|
||||
ipc::wstring internal(size_needed, L'\0');
|
||||
::MultiByteToWideChar(CP_UTF8, 0, &external[0], (int)external.size(), &internal[0], size_needed);
|
||||
return internal;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
@ -1,25 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/prod_cons.h"
|
||||
|
||||
#include "libipc/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> {
|
||||
using flag_t = Flag;
|
||||
|
||||
template <std::size_t DataSize, std::size_t AlignSize>
|
||||
using elems_t = circ::elem_array<ipc::prod_cons_impl<flag_t>, DataSize, AlignSize>;
|
||||
};
|
||||
|
||||
} // namespace policy
|
||||
} // namespace ipc
|
||||
@ -1,17 +0,0 @@
|
||||
#include "libipc/pool_alloc.h"
|
||||
|
||||
#include "libipc/memory/resource.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
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
|
||||
@ -1,315 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <utility>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
#include <cstdint>
|
||||
|
||||
#include "libipc/def.h"
|
||||
|
||||
#include "libipc/platform/detail.h"
|
||||
#include "libipc/circ/elem_def.h"
|
||||
#include "libipc/utility/log.h"
|
||||
#include "libipc/utility/utility.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(cache_line_size) std::atomic<circ::u2_t> rd_; // read index
|
||||
alignas(cache_line_size) std::atomic<circ::u2_t> wt_; // write index
|
||||
|
||||
constexpr circ::u2_t cursor() const noexcept {
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename F, typename E>
|
||||
bool push(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 F, typename E>
|
||||
bool pop(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 F,
|
||||
template <std::size_t, std::size_t> class E, std::size_t DS, std::size_t AS>
|
||||
bool pop(circ::u2_t& /*cur*/, F&& f, E<DS, AS>* elems) {
|
||||
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
|
||||
}
|
||||
if (rd_.compare_exchange_weak(cur_rd, cur_rd + 1, std::memory_order_release)) {
|
||||
std::forward<F>(f)(&(elems[circ::index_of(cur_rd)].data_));
|
||||
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(cache_line_size) std::atomic<circ::u2_t> ct_; // commit index
|
||||
|
||||
template <typename F, typename E>
|
||||
bool push(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_acq_rel)) {
|
||||
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_relaxed)) {
|
||||
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 F,
|
||||
template <std::size_t, std::size_t> class E, std::size_t DS, std::size_t AS>
|
||||
bool pop(circ::u2_t& /*cur*/, F&& f, E<DS, AS>* elems) {
|
||||
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 if (rd_.compare_exchange_weak(cur_rd, cur_rd + 1, std::memory_order_release)) {
|
||||
std::forward<F>(f)(&(elems[id_rd].data_));
|
||||
return true;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct prod_cons_impl<wr<relat::single, relat::multi, trans::broadcast>> {
|
||||
|
||||
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_rc_ { 0 }; // read-flag
|
||||
};
|
||||
|
||||
alignas(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 F, typename E>
|
||||
bool push(F&& f, E* elems) {
|
||||
E* el = elems + circ::index_of(wt_.load(std::memory_order_relaxed));
|
||||
auto cur_rc = el->f_rc_.exchange(~0ull, std::memory_order_acq_rel);
|
||||
// check for consumers to read this element
|
||||
if (cur_rc != 0) {
|
||||
return false; // full
|
||||
}
|
||||
std::forward<F>(f)(&(el->data_));
|
||||
wt_.fetch_add(1, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename F, typename E>
|
||||
bool pop(circ::u2_t& cur, F&& f, E* elems) {
|
||||
if (cur == cursor()) return false; // empty
|
||||
E* el = elems + circ::index_of(cur++);
|
||||
std::forward<F>(f)(&(el->data_));
|
||||
el->f_rc_.store(0, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct prod_cons_impl<wr<relat::multi, relat::multi, trans::broadcast>> {
|
||||
|
||||
using flag_t = std::uint64_t;
|
||||
|
||||
enum : flag_t {
|
||||
pushing = 1ull,
|
||||
pushed = ~0ull,
|
||||
popped = 0ull,
|
||||
};
|
||||
|
||||
template <std::size_t DataSize, std::size_t AlignSize>
|
||||
struct elem_t {
|
||||
std::aligned_storage_t<DataSize, AlignSize> data_ {};
|
||||
std::atomic<flag_t> f_rc_ { 0 }; // read-flag
|
||||
std::atomic<flag_t> f_ct_ { 0 }; // commit-flag
|
||||
};
|
||||
|
||||
alignas(cache_line_size) std::atomic<circ::u2_t> ct_; // commit index
|
||||
alignas(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 F, typename E>
|
||||
bool push(F&& f, E* elems) {
|
||||
E* el;
|
||||
circ::u2_t cur_ct, nxt_ct;
|
||||
for (unsigned k = 0;;) {
|
||||
auto cac_ct = ct_.load(std::memory_order_relaxed);
|
||||
cur_ct = cac_ct;
|
||||
nxt_ct = cur_ct + 1;
|
||||
el = elems + circ::index_of(cac_ct);
|
||||
for (unsigned k = 0;;) {
|
||||
auto cur_rc = el->f_rc_.load(std::memory_order_acquire);
|
||||
switch (cur_rc) {
|
||||
// helper
|
||||
case pushing:
|
||||
ct_.compare_exchange_strong(cac_ct, nxt_ct, std::memory_order_release);
|
||||
goto try_next;
|
||||
// full
|
||||
case pushed:
|
||||
return false;
|
||||
// writable
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (el->f_rc_.compare_exchange_weak(cur_rc, pushing, std::memory_order_release)) {
|
||||
break;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
ct_.compare_exchange_strong(cac_ct, nxt_ct, std::memory_order_relaxed);
|
||||
el->f_rc_.store(pushed, std::memory_order_relaxed);
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
break;
|
||||
try_next:
|
||||
ipc::yield(k);
|
||||
}
|
||||
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_relaxed)) {
|
||||
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 F, typename E, std::size_t N>
|
||||
bool pop(circ::u2_t& cur, F&& f, E(& elems)[N]) {
|
||||
for (unsigned k = 0;;) {
|
||||
auto cur_wt = wt_.load(std::memory_order_acquire);
|
||||
auto id_rd = circ::index_of(cur);
|
||||
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 {
|
||||
++cur;
|
||||
auto* el = elems + id_rd;
|
||||
std::forward<F>(f)(&(el->data_));
|
||||
el->f_rc_.store(popped, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
ipc::yield(k);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
@ -1,198 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <new>
|
||||
#include <utility> // [[since C++14]]: std::exchange
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <tuple>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <cassert> // assert
|
||||
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/shm.h"
|
||||
#include "libipc/rw_lock.h"
|
||||
|
||||
#include "libipc/utility/log.h"
|
||||
#include "libipc/platform/detail.h"
|
||||
#include "libipc/circ/elem_def.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
class queue_conn {
|
||||
protected:
|
||||
circ::cc_t connected_ = 0;
|
||||
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_ != 0;
|
||||
}
|
||||
|
||||
circ::cc_t connected_id() const noexcept {
|
||||
return connected_;
|
||||
}
|
||||
|
||||
template <typename Elems>
|
||||
auto connect(Elems* elems) noexcept
|
||||
/*needs 'optional' here*/
|
||||
-> std::tuple<bool, bool, decltype(std::declval<Elems>().cursor())> {
|
||||
if (elems == nullptr) return {};
|
||||
// if it's already connected, just return
|
||||
if (connected()) return {connected(), false, 0};
|
||||
connected_ = elems->connect_receiver();
|
||||
return {connected(), true, elems->cursor()};
|
||||
}
|
||||
|
||||
template <typename Elems>
|
||||
bool disconnect(Elems* elems) noexcept {
|
||||
if (elems == nullptr) return false;
|
||||
// if it's already disconnected, just return false
|
||||
if (!connected()) return false;
|
||||
elems->disconnect_receiver(std::exchange(connected_, 0));
|
||||
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;
|
||||
bool sender_flag_ = false;
|
||||
|
||||
public:
|
||||
using base_t::base_t;
|
||||
|
||||
queue_base() = default;
|
||||
|
||||
explicit queue_base(char const * name)
|
||||
: queue_base{} {
|
||||
elems_ = open<elems_t>(name);
|
||||
}
|
||||
|
||||
explicit queue_base(elems_t * elems) noexcept
|
||||
: queue_base{} {
|
||||
assert(elems != nullptr);
|
||||
elems_ = elems;
|
||||
}
|
||||
|
||||
/* not virtual */ ~queue_base() {
|
||||
base_t::close();
|
||||
}
|
||||
|
||||
elems_t * elems() noexcept { return elems_; }
|
||||
elems_t const * elems() const noexcept { return elems_; }
|
||||
|
||||
bool ready_sending() noexcept {
|
||||
if (elems_ == nullptr) return false;
|
||||
return sender_flag_ || (sender_flag_ = elems_->connect_sender());
|
||||
}
|
||||
|
||||
void shut_sending() noexcept {
|
||||
if (elems_ == nullptr) return;
|
||||
if (!sender_flag_) return;
|
||||
elems_->disconnect_sender();
|
||||
}
|
||||
|
||||
bool connect() noexcept {
|
||||
auto tp = base_t::connect(elems_);
|
||||
if (std::get<0>(tp) && std::get<1>(tp)) {
|
||||
cursor_ = std::get<2>(tp);
|
||||
return true;
|
||||
}
|
||||
return std::get<0>(tp);
|
||||
}
|
||||
|
||||
bool disconnect() noexcept {
|
||||
return base_t::disconnect(elems_);
|
||||
}
|
||||
|
||||
std::size_t conn_count() const noexcept {
|
||||
return (elems_ == nullptr) ? static_cast<std::size_t>(invalid_value) : elems_->conn_count();
|
||||
}
|
||||
|
||||
bool valid() const noexcept {
|
||||
return elems_ != nullptr;
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return !valid() || (cursor_ == elems_->cursor());
|
||||
}
|
||||
|
||||
template <typename T, typename F, typename... P>
|
||||
bool push(F&& prep, P&&... params) {
|
||||
if (elems_ == nullptr) return false;
|
||||
return elems_->push([&](void* p) {
|
||||
if (prep(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 final : 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>
|
||||
bool push(P&&... params) {
|
||||
return base_t::template push<T>(std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
bool pop(T& item) {
|
||||
return base_t::pop(item);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
@ -1,103 +0,0 @@
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "libipc/shm.h"
|
||||
|
||||
#include "libipc/utility/pimpl.h"
|
||||
#include "libipc/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 noexcept {
|
||||
return impl(p_)->m_ != nullptr;
|
||||
}
|
||||
|
||||
std::size_t handle::size() const noexcept {
|
||||
return impl(p_)->s_;
|
||||
}
|
||||
|
||||
char const * handle::name() const noexcept {
|
||||
return impl(p_)->n_.c_str();
|
||||
}
|
||||
|
||||
std::int32_t handle::ref() const noexcept {
|
||||
return shm::get_ref(impl(p_)->id_);
|
||||
}
|
||||
|
||||
void handle::sub_ref() noexcept {
|
||||
shm::sub_ref(impl(p_)->id_);
|
||||
}
|
||||
|
||||
bool handle::acquire(char const * name, std::size_t size, unsigned mode) {
|
||||
release();
|
||||
impl(p_)->id_ = shm::acquire((impl(p_)->n_ = name).c_str(), size, mode);
|
||||
impl(p_)->m_ = shm::get_mem(impl(p_)->id_, &(impl(p_)->s_));
|
||||
return valid();
|
||||
}
|
||||
|
||||
std::int32_t handle::release() {
|
||||
if (impl(p_)->id_ == nullptr) return -1;
|
||||
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
|
||||
@ -1,72 +0,0 @@
|
||||
|
||||
#include "libipc/condition.h"
|
||||
|
||||
#include "libipc/utility/pimpl.h"
|
||||
#include "libipc/memory/resource.h"
|
||||
#include "libipc/platform/detail.h"
|
||||
#if defined(IPC_OS_WINDOWS_)
|
||||
#include "libipc/platform/win/condition.h"
|
||||
#elif defined(IPC_OS_LINUX_)
|
||||
#include "libipc/platform/linux/condition.h"
|
||||
#elif defined(IPC_OS_QNX_)
|
||||
#include "libipc/platform/posix/condition.h"
|
||||
#else/*IPC_OS*/
|
||||
# error "Unsupported platform."
|
||||
#endif
|
||||
|
||||
namespace ipc {
|
||||
namespace sync {
|
||||
|
||||
class condition::condition_ : public ipc::pimpl<condition_> {
|
||||
public:
|
||||
ipc::detail::sync::condition cond_;
|
||||
};
|
||||
|
||||
condition::condition()
|
||||
: p_(p_->make()) {
|
||||
}
|
||||
|
||||
condition::condition(char const * name)
|
||||
: condition() {
|
||||
open(name);
|
||||
}
|
||||
|
||||
condition::~condition() {
|
||||
close();
|
||||
p_->clear();
|
||||
}
|
||||
|
||||
void const *condition::native() const noexcept {
|
||||
return impl(p_)->cond_.native();
|
||||
}
|
||||
|
||||
void *condition::native() noexcept {
|
||||
return impl(p_)->cond_.native();
|
||||
}
|
||||
|
||||
bool condition::valid() const noexcept {
|
||||
return impl(p_)->cond_.valid();
|
||||
}
|
||||
|
||||
bool condition::open(char const *name) noexcept {
|
||||
return impl(p_)->cond_.open(name);
|
||||
}
|
||||
|
||||
void condition::close() noexcept {
|
||||
impl(p_)->cond_.close();
|
||||
}
|
||||
|
||||
bool condition::wait(ipc::sync::mutex &mtx, std::uint64_t tm) noexcept {
|
||||
return impl(p_)->cond_.wait(mtx, tm);
|
||||
}
|
||||
|
||||
bool condition::notify(ipc::sync::mutex &mtx) noexcept {
|
||||
return impl(p_)->cond_.notify(mtx);
|
||||
}
|
||||
|
||||
bool condition::broadcast(ipc::sync::mutex &mtx) noexcept {
|
||||
return impl(p_)->cond_.broadcast(mtx);
|
||||
}
|
||||
|
||||
} // namespace sync
|
||||
} // namespace ipc
|
||||
@ -1,72 +0,0 @@
|
||||
|
||||
#include "libipc/mutex.h"
|
||||
|
||||
#include "libipc/utility/pimpl.h"
|
||||
#include "libipc/memory/resource.h"
|
||||
#include "libipc/platform/detail.h"
|
||||
#if defined(IPC_OS_WINDOWS_)
|
||||
#include "libipc/platform/win/mutex.h"
|
||||
#elif defined(IPC_OS_LINUX_)
|
||||
#include "libipc/platform/linux/mutex.h"
|
||||
#elif defined(IPC_OS_QNX_)
|
||||
#include "libipc/platform/posix/mutex.h"
|
||||
#else/*IPC_OS*/
|
||||
# error "Unsupported platform."
|
||||
#endif
|
||||
|
||||
namespace ipc {
|
||||
namespace sync {
|
||||
|
||||
class mutex::mutex_ : public ipc::pimpl<mutex_> {
|
||||
public:
|
||||
ipc::detail::sync::mutex lock_;
|
||||
};
|
||||
|
||||
mutex::mutex()
|
||||
: p_(p_->make()) {
|
||||
}
|
||||
|
||||
mutex::mutex(char const * name)
|
||||
: mutex() {
|
||||
open(name);
|
||||
}
|
||||
|
||||
mutex::~mutex() {
|
||||
close();
|
||||
p_->clear();
|
||||
}
|
||||
|
||||
void const *mutex::native() const noexcept {
|
||||
return impl(p_)->lock_.native();
|
||||
}
|
||||
|
||||
void *mutex::native() noexcept {
|
||||
return impl(p_)->lock_.native();
|
||||
}
|
||||
|
||||
bool mutex::valid() const noexcept {
|
||||
return impl(p_)->lock_.valid();
|
||||
}
|
||||
|
||||
bool mutex::open(char const *name) noexcept {
|
||||
return impl(p_)->lock_.open(name);
|
||||
}
|
||||
|
||||
void mutex::close() noexcept {
|
||||
impl(p_)->lock_.close();
|
||||
}
|
||||
|
||||
bool mutex::lock(std::uint64_t tm) noexcept {
|
||||
return impl(p_)->lock_.lock(tm);
|
||||
}
|
||||
|
||||
bool mutex::try_lock() noexcept(false) {
|
||||
return impl(p_)->lock_.try_lock();
|
||||
}
|
||||
|
||||
bool mutex::unlock() noexcept {
|
||||
return impl(p_)->lock_.unlock();
|
||||
}
|
||||
|
||||
} // namespace sync
|
||||
} // namespace ipc
|
||||
@ -1,66 +0,0 @@
|
||||
|
||||
#include "libipc/semaphore.h"
|
||||
|
||||
#include "libipc/utility/pimpl.h"
|
||||
#include "libipc/memory/resource.h"
|
||||
#include "libipc/platform/detail.h"
|
||||
#if defined(IPC_OS_WINDOWS_)
|
||||
#include "libipc/platform/win/semaphore.h"
|
||||
#elif defined(IPC_OS_LINUX_) || defined(IPC_OS_QNX_)
|
||||
#include "libipc/platform/posix/semaphore_impl.h"
|
||||
#else/*IPC_OS*/
|
||||
# error "Unsupported platform."
|
||||
#endif
|
||||
|
||||
namespace ipc {
|
||||
namespace sync {
|
||||
|
||||
class semaphore::semaphore_ : public ipc::pimpl<semaphore_> {
|
||||
public:
|
||||
ipc::detail::sync::semaphore sem_;
|
||||
};
|
||||
|
||||
semaphore::semaphore()
|
||||
: p_(p_->make()) {
|
||||
}
|
||||
|
||||
semaphore::semaphore(char const * name, std::uint32_t count)
|
||||
: semaphore() {
|
||||
open(name, count);
|
||||
}
|
||||
|
||||
semaphore::~semaphore() {
|
||||
close();
|
||||
p_->clear();
|
||||
}
|
||||
|
||||
void const *semaphore::native() const noexcept {
|
||||
return impl(p_)->sem_.native();
|
||||
}
|
||||
|
||||
void *semaphore::native() noexcept {
|
||||
return impl(p_)->sem_.native();
|
||||
}
|
||||
|
||||
bool semaphore::valid() const noexcept {
|
||||
return impl(p_)->sem_.valid();
|
||||
}
|
||||
|
||||
bool semaphore::open(char const *name, std::uint32_t count) noexcept {
|
||||
return impl(p_)->sem_.open(name, count);
|
||||
}
|
||||
|
||||
void semaphore::close() noexcept {
|
||||
impl(p_)->sem_.close();
|
||||
}
|
||||
|
||||
bool semaphore::wait(std::uint64_t tm) noexcept {
|
||||
return impl(p_)->sem_.wait(tm);
|
||||
}
|
||||
|
||||
bool semaphore::post(std::uint32_t count) noexcept {
|
||||
return impl(p_)->sem_.post(count);
|
||||
}
|
||||
|
||||
} // namespace sync
|
||||
} // namespace ipc
|
||||
@ -1,29 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits> // std::enable_if
|
||||
|
||||
namespace ipc {
|
||||
|
||||
// concept helpers
|
||||
|
||||
template <bool Cond, typename R = void>
|
||||
using require = typename std::enable_if<Cond, R>::type;
|
||||
|
||||
#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
|
||||
@ -1,103 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits> // std::aligned_storage_t
|
||||
#include <cstring> // std::memcmp
|
||||
#include <cstdint>
|
||||
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
using storage_id_t = std::int32_t;
|
||||
|
||||
template <std::size_t DataSize, std::size_t AlignSize>
|
||||
struct id_type;
|
||||
|
||||
template <std::size_t AlignSize>
|
||||
struct id_type<0, AlignSize> {
|
||||
uint_t<8> id_;
|
||||
|
||||
id_type& operator=(storage_id_t val) {
|
||||
id_ = static_cast<uint_t<8>>(val);
|
||||
return (*this);
|
||||
}
|
||||
|
||||
operator uint_t<8>() const {
|
||||
return id_;
|
||||
}
|
||||
};
|
||||
|
||||
template <std::size_t DataSize, std::size_t AlignSize>
|
||||
struct id_type : id_type<0, AlignSize> {
|
||||
std::aligned_storage_t<DataSize, AlignSize> data_;
|
||||
};
|
||||
|
||||
template <std::size_t DataSize = 0,
|
||||
std::size_t AlignSize = (ipc::detail::min)(DataSize, alignof(std::max_align_t))>
|
||||
class id_pool {
|
||||
|
||||
static constexpr std::size_t limited_max_count() {
|
||||
return ipc::detail::min<std::size_t>(large_msg_cache, (std::numeric_limits<uint_t<8>>::max)());
|
||||
}
|
||||
|
||||
public:
|
||||
enum : std::size_t {
|
||||
/* eliminate error: taking address of temporary */
|
||||
max_count = limited_max_count()
|
||||
};
|
||||
|
||||
private:
|
||||
id_type<DataSize, AlignSize> next_[max_count];
|
||||
uint_t<8> cursor_ = 0;
|
||||
bool prepared_ = false;
|
||||
|
||||
public:
|
||||
void prepare() {
|
||||
if (!prepared_ && this->invalid()) this->init();
|
||||
prepared_ = true;
|
||||
}
|
||||
|
||||
void init() {
|
||||
for (storage_id_t i = 0; i < max_count;) {
|
||||
i = next_[i] = (i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
bool invalid() const {
|
||||
static id_pool inv;
|
||||
return std::memcmp(this, &inv, sizeof(id_pool)) == 0;
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return cursor_ == max_count;
|
||||
}
|
||||
|
||||
storage_id_t acquire() {
|
||||
if (empty()) return -1;
|
||||
storage_id_t id = cursor_;
|
||||
cursor_ = next_[id]; // point to next
|
||||
return id;
|
||||
}
|
||||
|
||||
bool release(storage_id_t id) {
|
||||
if (id < 0) return false;
|
||||
next_[id] = cursor_;
|
||||
cursor_ = static_cast<uint_t<8>>(id); // put it back
|
||||
return true;
|
||||
}
|
||||
|
||||
void * at(storage_id_t id) { return &(next_[id].data_); }
|
||||
void const * at(storage_id_t id) const { return &(next_[id].data_); }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class obj_pool : public id_pool<sizeof(T), alignof(T)> {
|
||||
using base_t = id_pool<sizeof(T), alignof(T)>;
|
||||
|
||||
public:
|
||||
T * at(storage_id_t id) { return reinterpret_cast<T *>(base_t::at(id)); }
|
||||
T const * at(storage_id_t id) const { return reinterpret_cast<T const *>(base_t::at(id)); }
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdio>
|
||||
#include <utility>
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
template <typename O>
|
||||
void print(O out, char const * str) {
|
||||
std::fprintf(out, "%s", str);
|
||||
}
|
||||
|
||||
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 * str) {
|
||||
ipc::detail::print(stderr, str);
|
||||
}
|
||||
|
||||
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,64 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <new>
|
||||
#include <utility>
|
||||
|
||||
#include "libipc/platform/detail.h"
|
||||
#include "libipc/utility/concept.h"
|
||||
#include "libipc/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>
|
||||
IPC_CONSTEXPR_ auto make_impl(P&&... params) -> IsImplComfortable<T> {
|
||||
T* buf {};
|
||||
::new (&buf) T { std::forward<P>(params)... };
|
||||
return buf;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
IPC_CONSTEXPR_ auto impl(T* const (& p)) -> IsImplComfortable<T> {
|
||||
return reinterpret_cast<T*>(&const_cast<char &>(reinterpret_cast<char const &>(p)));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
IPC_CONSTEXPR_ auto clear_impl(T* p) -> IsImplComfortable<T, void> {
|
||||
if (p != nullptr) impl(p)->~T();
|
||||
}
|
||||
|
||||
template <typename T, typename... P>
|
||||
IPC_CONSTEXPR_ auto make_impl(P&&... params) -> IsImplUncomfortable<T> {
|
||||
return mem::alloc<T>(std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
IPC_CONSTEXPR_ auto clear_impl(T* p) -> IsImplUncomfortable<T, void> {
|
||||
mem::free(p);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
IPC_CONSTEXPR_ auto impl(T* const (& p)) -> IsImplUncomfortable<T> {
|
||||
return p;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct pimpl {
|
||||
template <typename... P>
|
||||
IPC_CONSTEXPR_ static T* make(P&&... params) {
|
||||
return make_impl<T>(std::forward<P>(params)...);
|
||||
}
|
||||
|
||||
IPC_CONSTEXPR_ void clear() {
|
||||
clear_impl(static_cast<T*>(const_cast<pimpl*>(this)));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
@ -1,64 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility> // std::forward, std::move
|
||||
#include <algorithm> // std::swap
|
||||
#include <type_traits> // std::decay
|
||||
|
||||
namespace ipc {
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// Execute guard function when the enclosing scope exits
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename F>
|
||||
class scope_guard {
|
||||
F destructor_;
|
||||
mutable bool dismiss_;
|
||||
|
||||
public:
|
||||
template <typename D>
|
||||
scope_guard(D && destructor)
|
||||
: destructor_(std::forward<D>(destructor))
|
||||
, dismiss_(false) {
|
||||
}
|
||||
|
||||
scope_guard(scope_guard&& rhs)
|
||||
: destructor_(std::move(rhs.destructor_))
|
||||
, dismiss_(true) /* dismiss rhs */ {
|
||||
std::swap(dismiss_, rhs.dismiss_);
|
||||
}
|
||||
|
||||
~scope_guard() {
|
||||
try { do_exit(); }
|
||||
/**
|
||||
* In the realm of exceptions, it is fundamental that you can do nothing
|
||||
* if your "undo/recover" action fails.
|
||||
*/
|
||||
catch (...) { /* Do nothing */ }
|
||||
}
|
||||
|
||||
void swap(scope_guard & rhs) {
|
||||
std::swap(destructor_, rhs.destructor_);
|
||||
std::swap(dismiss_ , rhs.dismiss_);
|
||||
}
|
||||
|
||||
void dismiss() const noexcept {
|
||||
dismiss_ = true;
|
||||
}
|
||||
|
||||
void do_exit() {
|
||||
if (!dismiss_) {
|
||||
dismiss_ = true;
|
||||
destructor_();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename D>
|
||||
constexpr auto guard(D && destructor) noexcept {
|
||||
return scope_guard<std::decay_t<D>> {
|
||||
std::forward<D>(destructor)
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
@ -1,64 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility> // std::forward, std::integer_sequence
|
||||
#include <cstddef> // std::size_t
|
||||
#include <new> // std::hardware_destructive_interference_size
|
||||
#include <type_traits> // std::is_trivially_copyable
|
||||
|
||||
#include "libipc/platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
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<std::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<std::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*/
|
||||
};
|
||||
|
||||
template <typename T, typename U>
|
||||
auto horrible_cast(U rhs) noexcept
|
||||
-> typename std::enable_if<std::is_trivially_copyable<T>::value
|
||||
&& std::is_trivially_copyable<U>::value, T>::type {
|
||||
union {
|
||||
T t;
|
||||
U u;
|
||||
} r = {};
|
||||
r.u = rhs;
|
||||
return r.t;
|
||||
}
|
||||
|
||||
IPC_CONSTEXPR_ std::size_t make_align(std::size_t align, std::size_t size) {
|
||||
// align must be 2^n
|
||||
return (size + align - 1) & ~(align - 1);
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
@ -1,81 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/mutex.h"
|
||||
#include "libipc/condition.h"
|
||||
#include "libipc/platform/detail.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace detail {
|
||||
|
||||
class waiter {
|
||||
ipc::sync::condition cond_;
|
||||
ipc::sync::mutex lock_;
|
||||
std::atomic<bool> quit_ {false};
|
||||
|
||||
public:
|
||||
waiter() = default;
|
||||
waiter(char const *name) {
|
||||
open(name);
|
||||
}
|
||||
|
||||
~waiter() {
|
||||
close();
|
||||
}
|
||||
|
||||
bool valid() const noexcept {
|
||||
return cond_.valid() && lock_.valid();
|
||||
}
|
||||
|
||||
bool open(char const *name) noexcept {
|
||||
quit_.store(false, std::memory_order_relaxed);
|
||||
if (!cond_.open((std::string{"_waiter_cond_"} + name).c_str())) {
|
||||
return false;
|
||||
}
|
||||
if (!lock_.open((std::string{"_waiter_lock_"} + name).c_str())) {
|
||||
cond_.close();
|
||||
return false;
|
||||
}
|
||||
return valid();
|
||||
}
|
||||
|
||||
void close() noexcept {
|
||||
cond_.close();
|
||||
lock_.close();
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool wait_if(F &&pred, std::uint64_t tm = ipc::invalid_value) noexcept {
|
||||
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> guard {lock_};
|
||||
while ([this, &pred] {
|
||||
return !quit_.load(std::memory_order_relaxed)
|
||||
&& std::forward<F>(pred)();
|
||||
}()) {
|
||||
if (!cond_.wait(lock_, tm)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool notify() noexcept {
|
||||
{ IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> guard {lock_}; } // barrier
|
||||
return cond_.notify(lock_);
|
||||
}
|
||||
|
||||
bool broadcast() noexcept {
|
||||
{ IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> guard {lock_}; } // barrier
|
||||
return cond_.broadcast(lock_);
|
||||
}
|
||||
|
||||
bool quit_waiting() {
|
||||
quit_.store(true, std::memory_order_release);
|
||||
return broadcast();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
@ -15,10 +15,7 @@ include_directories(
|
||||
${LIBIPC_PROJECT_DIR}/3rdparty
|
||||
${LIBIPC_PROJECT_DIR}/3rdparty/gtest/include)
|
||||
|
||||
file(GLOB SRC_FILES
|
||||
${LIBIPC_PROJECT_DIR}/test/*.cpp
|
||||
# ${LIBIPC_PROJECT_DIR}/test/profiler/*.cpp
|
||||
)
|
||||
file(GLOB SRC_FILES ${LIBIPC_PROJECT_DIR}/test/*.cpp)
|
||||
file(GLOB HEAD_FILES ${LIBIPC_PROJECT_DIR}/test/*.h)
|
||||
|
||||
add_executable(${PROJECT_NAME} ${SRC_FILES} ${HEAD_FILES})
|
||||
|
||||
86
test/test.h
86
test/test.h
@ -1,86 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "capo/stopwatch.hpp"
|
||||
|
||||
#include "thread_pool.h"
|
||||
|
||||
namespace ipc_ut {
|
||||
|
||||
template <typename Dur>
|
||||
struct unit;
|
||||
|
||||
template <> struct unit<std::chrono::nanoseconds> {
|
||||
constexpr static char const * str() noexcept {
|
||||
return "ns";
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct unit<std::chrono::microseconds> {
|
||||
constexpr static char const * str() noexcept {
|
||||
return "us";
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct unit<std::chrono::milliseconds> {
|
||||
constexpr static char const * str() noexcept {
|
||||
return "ms";
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct unit<std::chrono::seconds> {
|
||||
constexpr static char const * str() noexcept {
|
||||
return "sec";
|
||||
}
|
||||
};
|
||||
|
||||
struct test_stopwatch {
|
||||
capo::stopwatch<> sw_;
|
||||
std::atomic_flag started_ = ATOMIC_FLAG_INIT;
|
||||
|
||||
void start() {
|
||||
if (!started_.test_and_set()) {
|
||||
sw_.start();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename ToDur = std::chrono::nanoseconds>
|
||||
void print_elapsed(int N, int Loops, char const * message = "") {
|
||||
auto ts = sw_.elapsed<ToDur>();
|
||||
std::cout << "[" << N << ", \t" << Loops << "] " << message << "\t"
|
||||
<< (double(ts) / double(Loops)) << " " << unit<ToDur>::str() << std::endl;
|
||||
}
|
||||
|
||||
template <int Factor, typename ToDur = std::chrono::nanoseconds>
|
||||
void print_elapsed(int N, int M, int Loops, char const * message = "") {
|
||||
auto ts = sw_.elapsed<ToDur>();
|
||||
std::cout << "[" << N << "-" << M << ", \t" << Loops << "] " << message << "\t"
|
||||
<< (double(ts) / double(Factor ? (Loops * Factor) : (Loops * N))) << " " << unit<ToDur>::str() << std::endl;
|
||||
}
|
||||
|
||||
template <typename ToDur = std::chrono::nanoseconds>
|
||||
void print_elapsed(int N, int M, int Loops, char const * message = "") {
|
||||
print_elapsed<0, ToDur>(N, M, Loops, message);
|
||||
}
|
||||
};
|
||||
|
||||
inline static thread_pool & sender() {
|
||||
static thread_pool pool;
|
||||
return pool;
|
||||
}
|
||||
|
||||
inline static thread_pool & reader() {
|
||||
static thread_pool pool;
|
||||
return pool;
|
||||
}
|
||||
|
||||
} // namespace ipc_ut
|
||||
79
test/test_detect_plat.cpp
Normal file
79
test/test_detect_plat.cpp
Normal file
@ -0,0 +1,79 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "libipc/detect_plat.h"
|
||||
|
||||
namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(detect_plat, os) {
|
||||
#if defined(LIBIPC_OS_WINCE)
|
||||
std::cout << "LIBIPC_OS_WINCE\n";
|
||||
#elif defined(LIBIPC_OS_WIN)
|
||||
std::cout << "LIBIPC_OS_WIN\n";
|
||||
#elif defined(LIBIPC_OS_LINUX)
|
||||
std::cout << "LIBIPC_OS_LINUX\n";
|
||||
#elif defined(LIBIPC_OS_QNX)
|
||||
std::cout << "LIBIPC_OS_QNX\n";
|
||||
#elif defined(LIBIPC_OS_ANDROID)
|
||||
std::cout << "LIBIPC_OS_ANDROID\n";
|
||||
#else
|
||||
ASSERT_TRUE(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(detect_plat, cc) {
|
||||
#if defined(LIBIPC_CC_MSVC)
|
||||
std::cout << "LIBIPC_CC_MSVC\n";
|
||||
#elif defined(LIBIPC_CC_GNUC)
|
||||
std::cout << "LIBIPC_CC_GNUC\n";
|
||||
#else
|
||||
ASSERT_TRUE(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(detect_plat, byte_order) {
|
||||
auto is_endian_little = [] {
|
||||
union {
|
||||
std::int32_t a;
|
||||
std::int8_t b;
|
||||
} c;
|
||||
c.a = 1;
|
||||
return c.b == 1;
|
||||
};
|
||||
EXPECT_EQ(!!LIBIPC_ENDIAN_LIT, is_endian_little());
|
||||
EXPECT_NE(!!LIBIPC_ENDIAN_BIG, is_endian_little());
|
||||
}
|
||||
|
||||
TEST(detect_plat, fallthrough) {
|
||||
switch (0) {
|
||||
case 1:
|
||||
std::cout << "fallthrough 0\n";
|
||||
LIBIPC_FALLTHROUGH;
|
||||
case 0:
|
||||
std::cout << "fallthrough 1\n";
|
||||
LIBIPC_FALLTHROUGH;
|
||||
default:
|
||||
std::cout << "fallthrough default\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(detect_plat, unused) {
|
||||
LIBIPC_UNUSED int abc;
|
||||
}
|
||||
|
||||
TEST(detect_plat, likely_unlikely) {
|
||||
int xx = sizeof(int);
|
||||
if LIBIPC_LIKELY(xx < sizeof(long long)) {
|
||||
std::cout << "sizeof(int) < sizeof(long long)\n";
|
||||
} else if LIBIPC_UNLIKELY(xx < sizeof(char)) {
|
||||
std::cout << "sizeof(int) < sizeof(char)\n";
|
||||
} else {
|
||||
std::cout << "sizeof(int) < whatever\n";
|
||||
}
|
||||
}
|
||||
@ -1,183 +0,0 @@
|
||||
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
|
||||
#include "libipc/ipc.h"
|
||||
#include "libipc/buffer.h"
|
||||
#include "libipc/memory/resource.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "thread_pool.h"
|
||||
|
||||
#include "capo/random.hpp"
|
||||
|
||||
using namespace ipc;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int LoopCount = 10000;
|
||||
constexpr int MultiMax = 8;
|
||||
constexpr int TestBuffMax = 65536;
|
||||
|
||||
struct msg_head {
|
||||
int id_;
|
||||
};
|
||||
|
||||
class rand_buf : public buffer {
|
||||
public:
|
||||
rand_buf() {
|
||||
int size = capo::random<>{(int)sizeof(msg_head), TestBuffMax}();
|
||||
*this = buffer(new char[size], size, [](void * p, std::size_t) {
|
||||
delete [] static_cast<char *>(p);
|
||||
});
|
||||
}
|
||||
|
||||
rand_buf(msg_head const &msg) {
|
||||
*this = buffer(new msg_head{msg}, sizeof(msg), [](void * p, std::size_t) {
|
||||
delete static_cast<msg_head *>(p);
|
||||
});
|
||||
}
|
||||
|
||||
rand_buf(rand_buf &&) = default;
|
||||
rand_buf(rand_buf const & rhs) {
|
||||
if (rhs.empty()) return;
|
||||
void * mem = new char[rhs.size()];
|
||||
std::memcpy(mem, rhs.data(), rhs.size());
|
||||
*this = buffer(mem, rhs.size(), [](void * p, std::size_t) {
|
||||
delete [] static_cast<char *>(p);
|
||||
});
|
||||
}
|
||||
|
||||
rand_buf(buffer && rhs)
|
||||
: buffer(std::move(rhs)) {
|
||||
}
|
||||
|
||||
void set_id(int k) noexcept {
|
||||
get<msg_head *>()->id_ = k;
|
||||
}
|
||||
|
||||
int get_id() const noexcept {
|
||||
return get<msg_head *>()->id_;
|
||||
}
|
||||
|
||||
using buffer::operator=;
|
||||
};
|
||||
|
||||
template <relat Rp, relat Rc, trans Ts>
|
||||
void test_basic(char const * name) {
|
||||
using que_t = chan<Rp, Rc, Ts>;
|
||||
rand_buf test1, test2;
|
||||
|
||||
que_t que1 { name };
|
||||
EXPECT_FALSE(que1.send(test1));
|
||||
|
||||
que_t que2 { que1.name(), ipc::receiver };
|
||||
ASSERT_TRUE(que1.send(test1));
|
||||
|
||||
EXPECT_EQ(que2.recv(), test1);
|
||||
EXPECT_EQ(que2.recv(), test2);
|
||||
}
|
||||
|
||||
class data_set {
|
||||
std::vector<rand_buf> datas_;
|
||||
|
||||
public:
|
||||
data_set() {
|
||||
datas_.resize(LoopCount);
|
||||
for (int i = 0; i < LoopCount; ++i) {
|
||||
datas_[i].set_id(i);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<rand_buf> const &get() const noexcept {
|
||||
return datas_;
|
||||
}
|
||||
} const data_set__;
|
||||
|
||||
template <relat Rp, relat Rc, trans Ts, typename Que = chan<Rp, Rc, Ts>>
|
||||
void test_sr(char const * name, int s_cnt, int r_cnt) {
|
||||
ipc_ut::sender().start(static_cast<std::size_t>(s_cnt));
|
||||
ipc_ut::reader().start(static_cast<std::size_t>(r_cnt));
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
ipc_ut::test_stopwatch sw;
|
||||
|
||||
for (int k = 0; k < s_cnt; ++k) {
|
||||
ipc_ut::sender() << [name, &sw, r_cnt, k] {
|
||||
Que que { name, ipc::sender };
|
||||
ASSERT_TRUE(que.wait_for_recv(r_cnt));
|
||||
sw.start();
|
||||
for (int i = 0; i < (int)data_set__.get().size(); ++i) {
|
||||
ASSERT_TRUE(que.send(data_set__.get()[i]));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for (int k = 0; k < r_cnt; ++k) {
|
||||
ipc_ut::reader() << [name] {
|
||||
Que que { name, ipc::receiver };
|
||||
for (;;) {
|
||||
rand_buf got { que.recv() };
|
||||
ASSERT_FALSE(got.empty());
|
||||
int i = got.get_id();
|
||||
if (i == -1) {
|
||||
return;
|
||||
}
|
||||
ASSERT_TRUE((i >= 0) && (i < (int)data_set__.get().size()));
|
||||
auto const &data_set = data_set__.get()[i];
|
||||
if (data_set != got) {
|
||||
printf("data_set__.get()[%d] != got, size = %zd/%zd\n",
|
||||
i, data_set.size(), got.size());
|
||||
ASSERT_TRUE(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ipc_ut::sender().wait_for_done();
|
||||
Que que { name };
|
||||
ASSERT_TRUE(que.wait_for_recv(r_cnt));
|
||||
for (int k = 0; k < r_cnt; ++k) {
|
||||
que.send(rand_buf{msg_head{-1}});
|
||||
}
|
||||
ipc_ut::reader().wait_for_done();
|
||||
sw.print_elapsed<std::chrono::microseconds>(s_cnt, r_cnt, (int)data_set__.get().size(), name);
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
TEST(IPC, basic) {
|
||||
test_basic<relat::single, relat::single, trans::unicast >("ssu");
|
||||
//test_basic<relat::single, relat::multi , trans::unicast >("smu");
|
||||
//test_basic<relat::multi , relat::multi , trans::unicast >("mmu");
|
||||
test_basic<relat::single, relat::multi , trans::broadcast>("smb");
|
||||
test_basic<relat::multi , relat::multi , trans::broadcast>("mmb");
|
||||
}
|
||||
|
||||
TEST(IPC, 1v1) {
|
||||
test_sr<relat::single, relat::single, trans::unicast >("ssu", 1, 1);
|
||||
//test_sr<relat::single, relat::multi , trans::unicast >("smu", 1, 1);
|
||||
//test_sr<relat::multi , relat::multi , trans::unicast >("mmu", 1, 1);
|
||||
test_sr<relat::single, relat::multi , trans::broadcast>("smb", 1, 1);
|
||||
test_sr<relat::multi , relat::multi , trans::broadcast>("mmb", 1, 1);
|
||||
}
|
||||
|
||||
TEST(IPC, 1vN) {
|
||||
//test_sr<relat::single, relat::multi , trans::unicast >("smu", 1, MultiMax);
|
||||
//test_sr<relat::multi , relat::multi , trans::unicast >("mmu", 1, MultiMax);
|
||||
test_sr<relat::single, relat::multi , trans::broadcast>("smb", 1, MultiMax);
|
||||
test_sr<relat::multi , relat::multi , trans::broadcast>("mmb", 1, MultiMax);
|
||||
}
|
||||
|
||||
TEST(IPC, Nv1) {
|
||||
//test_sr<relat::multi , relat::multi , trans::unicast >("mmu", MultiMax, 1);
|
||||
test_sr<relat::multi , relat::multi , trans::broadcast>("mmb", MultiMax, 1);
|
||||
}
|
||||
|
||||
TEST(IPC, NvN) {
|
||||
//test_sr<relat::multi , relat::multi , trans::unicast >("mmu", MultiMax, MultiMax);
|
||||
test_sr<relat::multi , relat::multi , trans::broadcast>("mmb", MultiMax, MultiMax);
|
||||
}
|
||||
@ -1,218 +0,0 @@
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
|
||||
#include "capo/random.hpp"
|
||||
|
||||
#include "libipc/memory/resource.h"
|
||||
#include "libipc/pool_alloc.h"
|
||||
|
||||
// #include "gperftools/tcmalloc.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "thread_pool.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int DataMin = 4;
|
||||
constexpr int DataMax = 256;
|
||||
constexpr int LoopCount = 8388608;
|
||||
constexpr int ThreadMax = 8;
|
||||
|
||||
// constexpr int DataMin = 256;
|
||||
// constexpr int DataMax = 512;
|
||||
// constexpr int LoopCount = 2097152;
|
||||
|
||||
std::vector<std::size_t> sizes__;
|
||||
std::vector<void*> ptr_cache__[ThreadMax];
|
||||
|
||||
template <typename M>
|
||||
struct alloc_ix_t {
|
||||
static std::vector<int> ix_;
|
||||
static bool inited_;
|
||||
|
||||
alloc_ix_t() {
|
||||
if (inited_) return;
|
||||
inited_ = true;
|
||||
M::init();
|
||||
}
|
||||
|
||||
template <int ThreadsN>
|
||||
static int index(std::size_t /*pid*/, std::size_t /*k*/, std::size_t n) {
|
||||
return ix_[n];
|
||||
}
|
||||
};
|
||||
|
||||
template <typename M>
|
||||
std::vector<int> alloc_ix_t<M>::ix_(LoopCount);
|
||||
template <typename M>
|
||||
bool alloc_ix_t<M>::inited_ = false;
|
||||
|
||||
struct alloc_FIFO : alloc_ix_t<alloc_FIFO> {
|
||||
static void init() {
|
||||
for (int i = 0; i < LoopCount; ++i) {
|
||||
ix_[static_cast<std::size_t>(i)] = i;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct alloc_LIFO : alloc_ix_t<alloc_LIFO> {
|
||||
static void init() {
|
||||
for (int i = 0; i < LoopCount; ++i) {
|
||||
ix_[static_cast<std::size_t>(i)] = i;
|
||||
}
|
||||
}
|
||||
|
||||
template <int ThreadsN>
|
||||
static int index(std::size_t pid, std::size_t k, std::size_t n) {
|
||||
constexpr static int CacheSize = LoopCount / ThreadsN;
|
||||
if (k) {
|
||||
return ix_[(CacheSize * (2 * pid + 1)) - 1 - n];
|
||||
}
|
||||
else return ix_[n];
|
||||
}
|
||||
};
|
||||
|
||||
struct alloc_Random : alloc_ix_t<alloc_Random> {
|
||||
static void init() {
|
||||
capo::random<> rdm_index(0, LoopCount - 1);
|
||||
for (int i = 0; i < LoopCount; ++i) {
|
||||
ix_[static_cast<std::size_t>(i)] = rdm_index();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct Init {
|
||||
Init() {
|
||||
capo::random<> rdm{ DataMin, DataMax };
|
||||
for (int i = 0; i < LoopCount; ++i) {
|
||||
sizes__.emplace_back(static_cast<std::size_t>(rdm()));
|
||||
}
|
||||
for (auto& vec : ptr_cache__) {
|
||||
vec.resize(LoopCount, nullptr);
|
||||
}
|
||||
}
|
||||
} init__;
|
||||
|
||||
template <typename AllocT, int ThreadsN>
|
||||
void benchmark_alloc(char const * message) {
|
||||
std::string msg = std::to_string(ThreadsN) + "\t" + message;
|
||||
|
||||
constexpr static int CacheSize = LoopCount / ThreadsN;
|
||||
ipc_ut::sender().start(static_cast<std::size_t>(ThreadsN));
|
||||
ipc_ut::test_stopwatch sw;
|
||||
|
||||
for (int pid = 0; pid < ThreadsN; ++pid) {
|
||||
ipc_ut::sender() << [&, pid] {
|
||||
sw.start();
|
||||
for (int n = (CacheSize * pid); n < (CacheSize * (pid + 1)); ++n) {
|
||||
std::size_t s = sizes__[n];
|
||||
AllocT::free(AllocT::alloc(s), s);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ipc_ut::sender().wait_for_done();
|
||||
sw.print_elapsed<1>(DataMin, DataMax, LoopCount, msg.c_str());
|
||||
}
|
||||
|
||||
template <typename AllocT, typename ModeT, int ThreadsN>
|
||||
void benchmark_alloc(char const * message) {
|
||||
std::string msg = std::to_string(ThreadsN) + "\t" + message;
|
||||
|
||||
constexpr static int CacheSize = LoopCount / ThreadsN;
|
||||
ModeT mode;
|
||||
ipc_ut::sender().start(static_cast<std::size_t>(ThreadsN));
|
||||
ipc_ut::test_stopwatch sw;
|
||||
|
||||
for (int pid = 0; pid < ThreadsN; ++pid) {
|
||||
ipc_ut::sender() << [&, pid] {
|
||||
auto& vec = ptr_cache__[pid];
|
||||
sw.start();
|
||||
for (std::size_t k = 0; k < 2; ++k)
|
||||
for (int n = (CacheSize * pid); n < (CacheSize * (pid + 1)); ++n) {
|
||||
int m = mode.template index<ThreadsN>(pid, k, n);
|
||||
void*& p = vec[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;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ipc_ut::sender().wait_for_done();
|
||||
sw.print_elapsed<1>(DataMin, DataMax, LoopCount, msg.c_str());
|
||||
}
|
||||
|
||||
template <typename AllocT, typename ModeT, int ThreadsN>
|
||||
struct test_performance {
|
||||
static void start(char const * message) {
|
||||
test_performance<AllocT, ModeT, ThreadsN / 2>::start(message);
|
||||
benchmark_alloc<AllocT, ModeT, ThreadsN>(message);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename AllocT, typename ModeT>
|
||||
struct test_performance<AllocT, ModeT, 1> {
|
||||
static void start(char const * message) {
|
||||
benchmark_alloc<AllocT, ModeT, 1>(message);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename AllocT, int ThreadsN>
|
||||
struct test_performance<AllocT, void, ThreadsN> {
|
||||
static void start(char const * message) {
|
||||
test_performance<AllocT, void, ThreadsN / 2>::start(message);
|
||||
benchmark_alloc<AllocT, ThreadsN>(message);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename AllocT>
|
||||
struct test_performance<AllocT, void, 1> {
|
||||
static void start(char const * message) {
|
||||
benchmark_alloc<AllocT, 1>(message);
|
||||
}
|
||||
};
|
||||
|
||||
// class tc_alloc {
|
||||
// public:
|
||||
// static void clear() {}
|
||||
|
||||
// static void* alloc(std::size_t size) {
|
||||
// return size ? tc_malloc(size) : nullptr;
|
||||
// }
|
||||
|
||||
// static void free(void* p, std::size_t size) {
|
||||
// tc_free_sized(p, size);
|
||||
// }
|
||||
// };
|
||||
/*
|
||||
TEST(Memory, static_alloc) {
|
||||
test_performance<ipc::mem::static_alloc, void , ThreadMax>::start("alloc-free");
|
||||
test_performance<ipc::mem::static_alloc, alloc_FIFO , ThreadMax>::start("alloc-FIFO");
|
||||
test_performance<ipc::mem::static_alloc, alloc_LIFO , ThreadMax>::start("alloc-LIFO");
|
||||
test_performance<ipc::mem::static_alloc, alloc_Random, ThreadMax>::start("alloc-Rand");
|
||||
}
|
||||
|
||||
TEST(Memory, pool_alloc) {
|
||||
test_performance<ipc::mem::async_pool_alloc, void , ThreadMax>::start("alloc-free");
|
||||
test_performance<ipc::mem::async_pool_alloc, alloc_FIFO , ThreadMax>::start("alloc-FIFO");
|
||||
test_performance<ipc::mem::async_pool_alloc, alloc_LIFO , ThreadMax>::start("alloc-LIFO");
|
||||
test_performance<ipc::mem::async_pool_alloc, alloc_Random, ThreadMax>::start("alloc-Rand");
|
||||
}
|
||||
*/
|
||||
// TEST(Memory, tc_alloc) {
|
||||
// test_performance<tc_alloc, void , ThreadMax>::start();
|
||||
// test_performance<tc_alloc, alloc_FIFO , ThreadMax>::start();
|
||||
// test_performance<tc_alloc, alloc_LIFO , ThreadMax>::start();
|
||||
// test_performance<tc_alloc, alloc_Random, ThreadMax>::start();
|
||||
// }
|
||||
|
||||
} // internal-linkage
|
||||
@ -1,31 +0,0 @@
|
||||
#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || \
|
||||
defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || \
|
||||
defined(WINCE) || defined(_WIN32_WCE)
|
||||
|
||||
#include <locale>
|
||||
#include <iostream>
|
||||
//#include <fstream>
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "libipc/platform/win/to_tchar.h"
|
||||
|
||||
TEST(Platform, to_tchar) {
|
||||
char const *utf8 = "hello world, "
|
||||
"\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc"
|
||||
"\x8c\xe3\x81\x93\xe3\x82\x93\xe3"
|
||||
"\x81\xab\xe3\x81\xa1\xe3\x81\xaf";
|
||||
wchar_t const *utf16 = L"hello world, \u4f60\u597d\uff0c\u3053\u3093\u306b\u3061\u306f";
|
||||
{
|
||||
ipc::string str = ipc::detail::to_tchar<char>(utf8);
|
||||
EXPECT_STREQ(str.c_str(), utf8);
|
||||
}
|
||||
{
|
||||
ipc::wstring wtr = ipc::detail::to_tchar<wchar_t>(utf8);
|
||||
EXPECT_STREQ(wtr.c_str(), utf16);
|
||||
//std::ofstream out("out.txt", std::ios::binary|std::ios::out);
|
||||
//out.write((char const *)wtr.c_str(), wtr.size() * sizeof(wchar_t));
|
||||
}
|
||||
}
|
||||
|
||||
#endif/*WIN*/
|
||||
@ -1,299 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "libipc/prod_cons.h"
|
||||
#include "libipc/policy.h"
|
||||
#include "libipc/circ/elem_array.h"
|
||||
#include "libipc/queue.h"
|
||||
|
||||
#include "test.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct msg_t {
|
||||
int pid_;
|
||||
int dat_;
|
||||
|
||||
msg_t() = default;
|
||||
msg_t(int p, int d) : pid_(p), dat_(d) {}
|
||||
};
|
||||
|
||||
template <ipc::relat Rp, ipc::relat Rc, ipc::trans Ts>
|
||||
using queue_t = ipc::queue<msg_t, ipc::policy::choose<ipc::circ::elem_array, ipc::wr<Rp, Rc, Ts>>>;
|
||||
|
||||
template <ipc::relat Rp, ipc::relat Rc, ipc::trans Ts>
|
||||
struct elems_t : public queue_t<Rp, Rc, Ts>::elems_t {};
|
||||
|
||||
bool operator==(msg_t const & m1, msg_t const & m2) noexcept {
|
||||
return (m1.pid_ == m2.pid_) && (m1.dat_ == m2.dat_);
|
||||
}
|
||||
|
||||
bool operator!=(msg_t const & m1, msg_t const & m2) noexcept {
|
||||
return !(m1 == m2);
|
||||
}
|
||||
|
||||
constexpr int LoopCount = 1000000;
|
||||
constexpr int PushRetry = 1000000;
|
||||
constexpr int ThreadMax = 8;
|
||||
|
||||
template <typename Que>
|
||||
void push(Que & que, int p, int d) {
|
||||
for (int n = 0; !que.push([](void*) { return true; }, p, d); ++n) {
|
||||
ASSERT_NE(n, PushRetry);
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Que>
|
||||
msg_t pop(Que & que) {
|
||||
msg_t msg;
|
||||
while (!que.pop(msg)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
template <ipc::trans Ts>
|
||||
struct quitter;
|
||||
|
||||
template <>
|
||||
struct quitter<ipc::trans::unicast> {
|
||||
template <typename Que>
|
||||
static void emit(Que && que, int r_cnt) {
|
||||
for (int k = 0; k < r_cnt; ++k) {
|
||||
push(que, -1, -1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct quitter<ipc::trans::broadcast> {
|
||||
template <typename Que>
|
||||
static void emit(Que && que, int /*r_cnt*/) {
|
||||
push(que, -1, -1);
|
||||
}
|
||||
};
|
||||
|
||||
template <ipc::relat Rp, ipc::relat Rc, ipc::trans Ts>
|
||||
void test_sr(elems_t<Rp, Rc, Ts> && elems, int s_cnt, int r_cnt, char const * message) {
|
||||
ipc_ut::sender().start(static_cast<std::size_t>(s_cnt));
|
||||
ipc_ut::reader().start(static_cast<std::size_t>(r_cnt));
|
||||
ipc_ut::test_stopwatch sw;
|
||||
|
||||
for (int k = 0; k < s_cnt; ++k) {
|
||||
ipc_ut::sender() << [&elems, &sw, r_cnt, k] {
|
||||
queue_t<Rp, Rc, Ts> que { &elems };
|
||||
while (que.conn_count() != static_cast<std::size_t>(r_cnt)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
sw.start();
|
||||
for (int i = 0; i < LoopCount; ++i) {
|
||||
push(que, k, i);
|
||||
}
|
||||
};
|
||||
}
|
||||
for (int k = 0; k < r_cnt; ++k) {
|
||||
ipc_ut::reader() << [&elems, k] {
|
||||
queue_t<Rp, Rc, Ts> que { &elems };
|
||||
ASSERT_TRUE(que.connect());
|
||||
while (pop(que).pid_ >= 0) ;
|
||||
ASSERT_TRUE(que.disconnect());
|
||||
};
|
||||
}
|
||||
|
||||
ipc_ut::sender().wait_for_done();
|
||||
quitter<Ts>::emit(queue_t<Rp, Rc, Ts> { &elems }, r_cnt);
|
||||
ipc_ut::reader().wait_for_done();
|
||||
sw.print_elapsed(s_cnt, r_cnt, LoopCount, message);
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
TEST(Queue, check_size) {
|
||||
using el_t = elems_t<ipc::relat::single, ipc::relat::multi, ipc::trans::broadcast>;
|
||||
|
||||
std::cout << "cq_t::head_size = " << el_t::head_size << std::endl;
|
||||
std::cout << "cq_t::data_size = " << el_t::data_size << std::endl;
|
||||
std::cout << "cq_t::elem_size = " << el_t::elem_size << std::endl;
|
||||
std::cout << "cq_t::block_size = " << el_t::block_size << std::endl;
|
||||
|
||||
EXPECT_EQ(static_cast<std::size_t>(el_t::data_size), sizeof(msg_t));
|
||||
|
||||
std::cout << "sizeof(elems_t<s, m, b>) = " << sizeof(el_t) << std::endl;
|
||||
}
|
||||
|
||||
TEST(Queue, el_connection) {
|
||||
{
|
||||
elems_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> el;
|
||||
EXPECT_TRUE(el.connect_sender());
|
||||
for (std::size_t i = 0; i < 10000; ++i) {
|
||||
ASSERT_FALSE(el.connect_sender());
|
||||
}
|
||||
el.disconnect_sender();
|
||||
EXPECT_TRUE(el.connect_sender());
|
||||
}
|
||||
{
|
||||
elems_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::unicast> el;
|
||||
for (std::size_t i = 0; i < 10000; ++i) {
|
||||
ASSERT_TRUE(el.connect_sender());
|
||||
}
|
||||
el.disconnect_sender();
|
||||
EXPECT_TRUE(el.connect_sender());
|
||||
}
|
||||
{
|
||||
elems_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> el;
|
||||
auto cc = el.connect_receiver();
|
||||
EXPECT_NE(cc, 0);
|
||||
for (std::size_t i = 0; i < 10000; ++i) {
|
||||
ASSERT_EQ(el.connect_receiver(), 0);
|
||||
}
|
||||
EXPECT_EQ(el.disconnect_receiver(cc), 0);
|
||||
EXPECT_EQ(el.connect_receiver(), cc);
|
||||
}
|
||||
{
|
||||
elems_t<ipc::relat::single, ipc::relat::multi, ipc::trans::broadcast> el;
|
||||
auto cc = el.connect_receiver();
|
||||
EXPECT_EQ(cc, 1);
|
||||
for (std::size_t i = 0; i < 10000; ++i) {
|
||||
ASSERT_NE(el.connect_receiver(), 0);
|
||||
}
|
||||
EXPECT_EQ(el.disconnect_receiver(cc), 10000);
|
||||
EXPECT_EQ(el.connect_receiver(), 10000 + cc);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Queue, connection) {
|
||||
{
|
||||
elems_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> el;
|
||||
queue_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> que{&el};
|
||||
// sending
|
||||
for (std::size_t i = 0; i < 10000; ++i) {
|
||||
ASSERT_TRUE(que.ready_sending());
|
||||
}
|
||||
for (std::size_t i = 0; i < 10000; ++i) {
|
||||
queue_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> que{&el};
|
||||
ASSERT_FALSE(que.ready_sending());
|
||||
}
|
||||
for (std::size_t i = 0; i < 10000; ++i) {
|
||||
que.shut_sending();
|
||||
}
|
||||
{
|
||||
queue_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> que{&el};
|
||||
EXPECT_TRUE(que.ready_sending());
|
||||
}
|
||||
// receiving
|
||||
for (std::size_t i = 0; i < 10000; ++i) {
|
||||
ASSERT_TRUE(que.connect());
|
||||
}
|
||||
for (std::size_t i = 0; i < 10000; ++i) {
|
||||
queue_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> que{&el};
|
||||
ASSERT_FALSE(que.connect());
|
||||
}
|
||||
EXPECT_TRUE(que.disconnect());
|
||||
for (std::size_t i = 0; i < 10000; ++i) {
|
||||
ASSERT_FALSE(que.disconnect());
|
||||
}
|
||||
{
|
||||
queue_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> que{&el};
|
||||
EXPECT_TRUE(que.connect());
|
||||
}
|
||||
for (std::size_t i = 0; i < 10000; ++i) {
|
||||
queue_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> que{&el};
|
||||
ASSERT_FALSE(que.connect());
|
||||
}
|
||||
}
|
||||
{
|
||||
elems_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> el;
|
||||
queue_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> que{&el};
|
||||
// sending
|
||||
for (std::size_t i = 0; i < 10000; ++i) {
|
||||
ASSERT_TRUE(que.ready_sending());
|
||||
}
|
||||
for (std::size_t i = 0; i < 10000; ++i) {
|
||||
queue_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> que{&el};
|
||||
ASSERT_TRUE(que.ready_sending());
|
||||
}
|
||||
for (std::size_t i = 0; i < 10000; ++i) {
|
||||
que.shut_sending();
|
||||
}
|
||||
for (std::size_t i = 0; i < 10000; ++i) {
|
||||
queue_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> que{&el};
|
||||
ASSERT_TRUE(que.ready_sending());
|
||||
}
|
||||
// receiving
|
||||
for (std::size_t i = 0; i < 10000; ++i) {
|
||||
ASSERT_TRUE(que.connect());
|
||||
}
|
||||
for (std::size_t i = 0; i < 10000; ++i) {
|
||||
queue_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> que{&el};
|
||||
ASSERT_TRUE(que.connect());
|
||||
}
|
||||
ASSERT_TRUE(que.disconnect());
|
||||
for (std::size_t i = 0; i < 10000; ++i) {
|
||||
ASSERT_FALSE(que.disconnect());
|
||||
}
|
||||
for (std::size_t i = 0; i < 10000; ++i) {
|
||||
queue_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> que{&el};
|
||||
ASSERT_TRUE(que.connect());
|
||||
ASSERT_TRUE(que.disconnect());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Queue, prod_cons_1v1_unicast) {
|
||||
test_sr(elems_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast>{}, 1, 1, "ssu");
|
||||
test_sr(elems_t<ipc::relat::single, ipc::relat::multi , ipc::trans::unicast>{}, 1, 1, "smu");
|
||||
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::unicast>{}, 1, 1, "mmu");
|
||||
}
|
||||
|
||||
TEST(Queue, prod_cons_1v1_broadcast) {
|
||||
test_sr(elems_t<ipc::relat::single, ipc::relat::multi , ipc::trans::broadcast>{}, 1, 1, "smb");
|
||||
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::broadcast>{}, 1, 1, "mmb");
|
||||
}
|
||||
|
||||
TEST(Queue, prod_cons_1vN_unicast) {
|
||||
for (int i = 1; i <= ThreadMax; ++i) {
|
||||
test_sr(elems_t<ipc::relat::single, ipc::relat::multi , ipc::trans::unicast>{}, 1, i, "smu");
|
||||
}
|
||||
for (int i = 1; i <= ThreadMax; ++i) {
|
||||
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::unicast>{}, 1, i, "mmu");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Queue, prod_cons_1vN_broadcast) {
|
||||
for (int i = 1; i <= ThreadMax; ++i) {
|
||||
test_sr(elems_t<ipc::relat::single, ipc::relat::multi , ipc::trans::broadcast>{}, 1, i, "smb");
|
||||
}
|
||||
for (int i = 1; i <= ThreadMax; ++i) {
|
||||
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::broadcast>{}, 1, i, "mmb");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Queue, prod_cons_NvN_unicast) {
|
||||
for (int i = 1; i <= ThreadMax; ++i) {
|
||||
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::unicast>{}, 1, i, "mmu");
|
||||
}
|
||||
for (int i = 1; i <= ThreadMax; ++i) {
|
||||
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::unicast>{}, i, 1, "mmu");
|
||||
}
|
||||
for (int i = 1; i <= ThreadMax; ++i) {
|
||||
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::unicast>{}, i, i, "mmu");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Queue, prod_cons_NvN_broadcast) {
|
||||
for (int i = 1; i <= ThreadMax; ++i) {
|
||||
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::broadcast>{}, 1, i, "mmb");
|
||||
}
|
||||
for (int i = 1; i <= ThreadMax; ++i) {
|
||||
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::broadcast>{}, i, 1, "mmb");
|
||||
}
|
||||
for (int i = 1; i <= ThreadMax; ++i) {
|
||||
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::broadcast>{}, i, i, "mmb");
|
||||
}
|
||||
}
|
||||
@ -1,102 +0,0 @@
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <thread>
|
||||
|
||||
#include "libipc/shm.h"
|
||||
#include "test.h"
|
||||
|
||||
using namespace ipc::shm;
|
||||
|
||||
namespace {
|
||||
|
||||
TEST(SHM, acquire) {
|
||||
handle shm_hd;
|
||||
EXPECT_FALSE(shm_hd.valid());
|
||||
|
||||
EXPECT_TRUE(shm_hd.acquire("my-test-1", 1024));
|
||||
EXPECT_TRUE(shm_hd.valid());
|
||||
EXPECT_STREQ(shm_hd.name(), "my-test-1");
|
||||
|
||||
EXPECT_TRUE(shm_hd.acquire("my-test-2", 2048));
|
||||
EXPECT_TRUE(shm_hd.valid());
|
||||
EXPECT_STREQ(shm_hd.name(), "my-test-2");
|
||||
|
||||
EXPECT_TRUE(shm_hd.acquire("my-test-3", 4096));
|
||||
EXPECT_TRUE(shm_hd.valid());
|
||||
EXPECT_STREQ(shm_hd.name(), "my-test-3");
|
||||
}
|
||||
|
||||
TEST(SHM, release) {
|
||||
handle shm_hd;
|
||||
EXPECT_FALSE(shm_hd.valid());
|
||||
shm_hd.release();
|
||||
EXPECT_FALSE(shm_hd.valid());
|
||||
EXPECT_TRUE(shm_hd.acquire("release-test-1", 512));
|
||||
EXPECT_TRUE(shm_hd.valid());
|
||||
shm_hd.release();
|
||||
EXPECT_FALSE(shm_hd.valid());
|
||||
}
|
||||
|
||||
TEST(SHM, get) {
|
||||
handle shm_hd;
|
||||
EXPECT_TRUE(shm_hd.get() == nullptr);
|
||||
EXPECT_TRUE(shm_hd.acquire("get-test", 2048));
|
||||
|
||||
auto mem = shm_hd.get();
|
||||
EXPECT_TRUE(mem != nullptr);
|
||||
EXPECT_TRUE(mem == shm_hd.get());
|
||||
|
||||
std::uint8_t buf[1024] = {};
|
||||
EXPECT_TRUE(memcmp(mem, buf, sizeof(buf)) == 0);
|
||||
|
||||
handle shm_other(shm_hd.name(), shm_hd.size());
|
||||
EXPECT_TRUE(shm_other.get() != shm_hd.get());
|
||||
}
|
||||
|
||||
TEST(SHM, hello) {
|
||||
handle shm_hd;
|
||||
EXPECT_TRUE(shm_hd.acquire("hello-test", 128));
|
||||
auto mem = shm_hd.get();
|
||||
EXPECT_TRUE(mem != nullptr);
|
||||
|
||||
constexpr char hello[] = "hello!";
|
||||
std::memcpy(mem, hello, sizeof(hello));
|
||||
EXPECT_STREQ((char const *)shm_hd.get(), hello);
|
||||
|
||||
shm_hd.release();
|
||||
EXPECT_TRUE(shm_hd.get() == nullptr);
|
||||
EXPECT_TRUE(shm_hd.acquire("hello-test", 1024));
|
||||
|
||||
mem = shm_hd.get();
|
||||
EXPECT_TRUE(mem != nullptr);
|
||||
std::uint8_t buf[1024] = {};
|
||||
EXPECT_TRUE(memcmp(mem, buf, sizeof(buf)) == 0);
|
||||
|
||||
std::memcpy(mem, hello, sizeof(hello));
|
||||
EXPECT_STREQ((char const *)shm_hd.get(), hello);
|
||||
}
|
||||
|
||||
TEST(SHM, mt) {
|
||||
handle shm_hd;
|
||||
EXPECT_TRUE(shm_hd.acquire("mt-test", 256));
|
||||
constexpr char hello[] = "hello!";
|
||||
std::memcpy(shm_hd.get(), hello, sizeof(hello));
|
||||
|
||||
std::thread {
|
||||
[&shm_hd] {
|
||||
handle shm_mt(shm_hd.name(), shm_hd.size());
|
||||
shm_hd.release();
|
||||
constexpr char hello[] = "hello!";
|
||||
EXPECT_STREQ((char const *)shm_mt.get(), hello);
|
||||
}
|
||||
}.join();
|
||||
|
||||
EXPECT_TRUE(shm_hd.get() == nullptr);
|
||||
EXPECT_FALSE(shm_hd.valid());
|
||||
|
||||
EXPECT_TRUE(shm_hd.acquire("mt-test", 1024));
|
||||
std::uint8_t buf[1024] = {};
|
||||
EXPECT_TRUE(memcmp(shm_hd.get(), buf, sizeof(buf)) == 0);
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
@ -1,208 +0,0 @@
|
||||
|
||||
#include <thread>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
#include <deque>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "libipc/platform/detail.h"
|
||||
#if defined(IPC_OS_LINUX_)
|
||||
#include <pthread.h>
|
||||
#include <time.h>
|
||||
|
||||
TEST(PThread, Robust) {
|
||||
pthread_mutexattr_t ma;
|
||||
pthread_mutexattr_init(&ma);
|
||||
pthread_mutexattr_setpshared(&ma, PTHREAD_PROCESS_SHARED);
|
||||
pthread_mutexattr_setrobust(&ma, PTHREAD_MUTEX_ROBUST);
|
||||
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
pthread_mutex_init(&mutex, &ma);
|
||||
|
||||
std::thread{[&mutex] {
|
||||
pthread_mutex_lock(&mutex);
|
||||
// pthread_mutex_unlock(&mutex);
|
||||
}}.join();
|
||||
|
||||
struct timespec tout;
|
||||
clock_gettime(CLOCK_REALTIME, &tout);
|
||||
int r = pthread_mutex_timedlock(&mutex, &tout);
|
||||
EXPECT_EQ(r, EOWNERDEAD);
|
||||
|
||||
pthread_mutex_consistent(&mutex);
|
||||
pthread_mutex_unlock(&mutex);
|
||||
pthread_mutex_destroy(&mutex);
|
||||
}
|
||||
#elif defined(IPC_OS_WINDOWS_)
|
||||
#include <Windows.h>
|
||||
#include <tchar.h>
|
||||
|
||||
TEST(PThread, Robust) {
|
||||
HANDLE lock = CreateMutex(NULL, FALSE, _T("test-robust"));
|
||||
std::thread{[] {
|
||||
HANDLE lock = CreateMutex(NULL, FALSE, _T("test-robust"));
|
||||
WaitForSingleObject(lock, 0);
|
||||
}}.join();
|
||||
|
||||
DWORD r = WaitForSingleObject(lock, 0);
|
||||
EXPECT_EQ(r, WAIT_ABANDONED);
|
||||
|
||||
CloseHandle(lock);
|
||||
}
|
||||
#endif // OS
|
||||
|
||||
#include "libipc/mutex.h"
|
||||
|
||||
TEST(Sync, Mutex) {
|
||||
ipc::sync::mutex lock;
|
||||
EXPECT_TRUE(lock.open("test-mutex-robust"));
|
||||
std::thread{[] {
|
||||
ipc::sync::mutex lock {"test-mutex-robust"};
|
||||
EXPECT_TRUE(lock.valid());
|
||||
EXPECT_TRUE(lock.lock());
|
||||
}}.join();
|
||||
|
||||
EXPECT_THROW(lock.try_lock(), std::system_error);
|
||||
|
||||
// int i = 0;
|
||||
// EXPECT_TRUE(lock.lock());
|
||||
// i = 100;
|
||||
// auto t2 = std::thread{[&i] {
|
||||
// ipc::sync::mutex lock {"test-mutex-robust"};
|
||||
// EXPECT_TRUE(lock.valid());
|
||||
// EXPECT_FALSE(lock.try_lock());
|
||||
// EXPECT_TRUE(lock.lock());
|
||||
// i += i;
|
||||
// EXPECT_TRUE(lock.unlock());
|
||||
// }};
|
||||
// std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
// EXPECT_EQ(i, 100);
|
||||
// EXPECT_TRUE(lock.unlock());
|
||||
// t2.join();
|
||||
// EXPECT_EQ(i, 200);
|
||||
}
|
||||
|
||||
#include "libipc/semaphore.h"
|
||||
|
||||
TEST(Sync, Semaphore) {
|
||||
ipc::sync::semaphore sem;
|
||||
EXPECT_TRUE(sem.open("test-sem"));
|
||||
std::thread{[] {
|
||||
ipc::sync::semaphore sem {"test-sem"};
|
||||
EXPECT_TRUE(sem.post(1000));
|
||||
}}.join();
|
||||
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
EXPECT_TRUE(sem.wait(0));
|
||||
}
|
||||
EXPECT_FALSE(sem.wait(0));
|
||||
}
|
||||
|
||||
#include "libipc/condition.h"
|
||||
|
||||
TEST(Sync, Condition) {
|
||||
ipc::sync::condition cond;
|
||||
EXPECT_TRUE(cond.open("test-cond"));
|
||||
ipc::sync::mutex lock;
|
||||
EXPECT_TRUE(lock.open("test-mutex"));
|
||||
std::deque<int> que;
|
||||
|
||||
auto job = [&que](int num) {
|
||||
ipc::sync::condition cond {"test-cond"};
|
||||
ipc::sync::mutex lock {"test-mutex"};
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
int val = 0;
|
||||
{
|
||||
std::lock_guard<ipc::sync::mutex> guard {lock};
|
||||
while (que.empty()) {
|
||||
ASSERT_TRUE(cond.wait(lock));
|
||||
}
|
||||
val = que.front();
|
||||
que.pop_front();
|
||||
}
|
||||
EXPECT_NE(val, 0);
|
||||
std::printf("test-cond-%d: %d\n", num, val);
|
||||
}
|
||||
for (;;) {
|
||||
int val = 0;
|
||||
{
|
||||
std::lock_guard<ipc::sync::mutex> guard {lock};
|
||||
while (que.empty()) {
|
||||
EXPECT_TRUE(cond.wait(lock, 1000));
|
||||
}
|
||||
val = que.front();
|
||||
que.pop_front();
|
||||
}
|
||||
if (val == 0) {
|
||||
std::printf("test-cond-%d: exit.\n", num);
|
||||
return;
|
||||
}
|
||||
std::printf("test-cond-%d: %d\n", num, val);
|
||||
}
|
||||
};
|
||||
std::array<std::thread, 10> test_conds;
|
||||
for (int i = 0; i < (int)test_conds.size(); ++i) {
|
||||
test_conds[i] = std::thread{job, i};
|
||||
}
|
||||
|
||||
for (int i = 1; i < 100; ++i) {
|
||||
{
|
||||
std::lock_guard<ipc::sync::mutex> guard {lock};
|
||||
que.push_back(i);
|
||||
ASSERT_TRUE(cond.notify(lock));
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
}
|
||||
for (int i = 1; i < 100; ++i) {
|
||||
{
|
||||
std::lock_guard<ipc::sync::mutex> guard {lock};
|
||||
que.push_back(i);
|
||||
ASSERT_TRUE(cond.broadcast(lock));
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
}
|
||||
{
|
||||
std::lock_guard<ipc::sync::mutex> guard {lock};
|
||||
for (int i = 0; i < (int)test_conds.size(); ++i) {
|
||||
que.push_back(0);
|
||||
}
|
||||
ASSERT_TRUE(cond.broadcast(lock));
|
||||
}
|
||||
|
||||
for (auto &t : test_conds) t.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* https://stackoverflow.com/questions/51730660/is-this-a-bug-in-glibc-pthread
|
||||
*/
|
||||
TEST(Sync, ConditionRobust) {
|
||||
printf("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 1\n");
|
||||
ipc::sync::condition cond {"test-cond"};
|
||||
printf("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 2\n");
|
||||
ipc::sync::mutex lock {"test-mutex"};
|
||||
printf("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 3\n");
|
||||
ASSERT_TRUE(lock.lock());
|
||||
std::thread unlock {[] {
|
||||
printf("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWW 1\n");
|
||||
ipc::sync::condition cond {"test-cond"};
|
||||
printf("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWW 2\n");
|
||||
ipc::sync::mutex lock {"test-mutex"};
|
||||
printf("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWW 3\n");
|
||||
{
|
||||
std::lock_guard<ipc::sync::mutex> guard {lock};
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
printf("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWW 4\n");
|
||||
ASSERT_TRUE(cond.broadcast(lock));
|
||||
printf("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWW 5\n");
|
||||
}};
|
||||
printf("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 4\n");
|
||||
ASSERT_TRUE(cond.wait(lock));
|
||||
printf("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 5\n");
|
||||
ASSERT_TRUE(lock.unlock());
|
||||
printf("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 6\n");
|
||||
unlock.join();
|
||||
}
|
||||
@ -1,203 +0,0 @@
|
||||
#include <type_traits>
|
||||
#include <iostream>
|
||||
#include <shared_mutex> // std::shared_lock
|
||||
#include <utility>
|
||||
#include <thread>
|
||||
#include <future> // std::async
|
||||
#include <atomic>
|
||||
#include <string> // std::string
|
||||
|
||||
#include "capo/spin_lock.hpp"
|
||||
#include "capo/type_name.hpp"
|
||||
|
||||
#include "libipc/rw_lock.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "thread_pool.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int LoopCount = 100000;
|
||||
constexpr int ThreadMax = 8;
|
||||
|
||||
template <typename T>
|
||||
constexpr T acc(T b, T e) noexcept {
|
||||
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 Loops = LoopCount>
|
||||
void benchmark_lc(int w, int r, char const * message) {
|
||||
ipc_ut::sender().start(static_cast<std::size_t>(w));
|
||||
ipc_ut::reader().start(static_cast<std::size_t>(r));
|
||||
ipc_ut::test_stopwatch sw;
|
||||
|
||||
std::uint64_t data = 0;
|
||||
std::uint64_t sum = acc<std::uint64_t>(1, Loops) * w;
|
||||
Lc lc;
|
||||
|
||||
for (int k = 0; k < r; ++k) {
|
||||
ipc_ut::reader() << [sum, &lc, &data] {
|
||||
while (1) {
|
||||
{
|
||||
std::shared_lock<Lc> guard { lc };
|
||||
if (data == sum) break;
|
||||
}
|
||||
std::this_thread::yield();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for (int k = 0; k < w; ++k) {
|
||||
ipc_ut::sender() << [&sw, &lc, &data] {
|
||||
sw.start();
|
||||
for (int i = 1; i <= Loops; ++i) {
|
||||
{
|
||||
std::lock_guard<Lc> guard { lc };
|
||||
data += i;
|
||||
}
|
||||
std::this_thread::yield();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ipc_ut::sender().wait_for_done();
|
||||
sw.print_elapsed(w, r, Loops, message);
|
||||
ipc_ut::reader().wait_for_done();
|
||||
}
|
||||
|
||||
void test_lock_performance(int w, int r) {
|
||||
std::cout << "test_lock_performance: [" << w << "-" << r << "]" << std::endl;
|
||||
benchmark_lc<ipc::rw_lock >(w, r, "ipc::rw_lock");
|
||||
benchmark_lc<lc_wrapper< ipc::spin_lock>>(w, r, "ipc::spin_lock");
|
||||
benchmark_lc<lc_wrapper<capo::spin_lock>>(w, r, "capo::spin_lock");
|
||||
benchmark_lc<lc_wrapper<std::mutex> >(w, r, "std::mutex");
|
||||
// benchmark_lc<std::shared_mutex >(w, r, "std::shared_mutex");
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
//TEST(Thread, rw_lock) {
|
||||
// for (int i = 1; i <= ThreadMax; ++i) test_lock_performance(i, 0);
|
||||
// for (int i = 1; i <= ThreadMax; ++i) test_lock_performance(1, i);
|
||||
// for (int i = 2; i <= ThreadMax; ++i) test_lock_performance(i, i);
|
||||
//}
|
||||
|
||||
#if 0 // disable ipc::tls
|
||||
TEST(Thread, tls_main_thread) {
|
||||
ipc::tls::pointer<int> p;
|
||||
EXPECT_FALSE(p);
|
||||
EXPECT_NE(p.create(123), nullptr);
|
||||
EXPECT_EQ(*p, 123);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct Foo {
|
||||
std::atomic<int> * px_;
|
||||
Foo(std::atomic<int> * px) : px_(px) {}
|
||||
~Foo() {
|
||||
px_->fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Thread, tls_create_once) {
|
||||
std::atomic<int> x { 0 };
|
||||
Foo { &x };
|
||||
EXPECT_EQ(x, 1);
|
||||
{
|
||||
ipc::tls::pointer<Foo> foo;
|
||||
EXPECT_NE(foo.create(&x), nullptr);
|
||||
EXPECT_EQ(x, 1);
|
||||
|
||||
std::thread {[&foo, &x] {
|
||||
auto foo1 = foo.create_once(&x);
|
||||
auto foo2 = foo.create_once(&x);
|
||||
EXPECT_EQ(foo1, foo2);
|
||||
}}.join();
|
||||
EXPECT_EQ(x, 2);
|
||||
}
|
||||
EXPECT_EQ(x, 3);
|
||||
}
|
||||
|
||||
TEST(Thread, tls_multi_thread) {
|
||||
std::atomic<int> x { 0 };
|
||||
{
|
||||
ipc::tls::pointer<Foo> foo; // no create
|
||||
EXPECT_EQ(x, 0);
|
||||
|
||||
ipc_ut::thread_pool pool { 10 };
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
pool << [&foo, &x] {
|
||||
// foo.create_once(&x);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
};
|
||||
}
|
||||
pool.wait_for_done();
|
||||
EXPECT_EQ(x, 0); // thread_pool hasn't destructed.
|
||||
}
|
||||
// EXPECT_EQ(x, 10);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename Tls, typename T>
|
||||
void benchmark_tls(char const * message) {
|
||||
ipc_ut::thread_pool pool { static_cast<std::size_t>(ThreadMax) };
|
||||
ipc_ut::test_stopwatch sw;
|
||||
pool.wait_for_started();
|
||||
|
||||
for (int k = 0; k < ThreadMax; ++k) {
|
||||
pool << [&sw] {
|
||||
sw.start();
|
||||
for (int i = 0; i < LoopCount * 10; ++i) {
|
||||
*Tls::template get<T>() = i;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pool.wait_for_done();
|
||||
sw.print_elapsed(ThreadMax, LoopCount * 10, message);
|
||||
}
|
||||
|
||||
struct ipc_tls {
|
||||
template <typename T>
|
||||
static T * get() {
|
||||
static ipc::tls::pointer<T> p;
|
||||
return p.create_once();
|
||||
}
|
||||
};
|
||||
|
||||
struct std_tls {
|
||||
template <typename T>
|
||||
static T * get() {
|
||||
thread_local T p;
|
||||
return &p;
|
||||
}
|
||||
};
|
||||
|
||||
struct Str {
|
||||
std::string str_;
|
||||
|
||||
Str & operator=(int i) {
|
||||
str_ = std::to_string(i);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
TEST(Thread, tls_benchmark) {
|
||||
benchmark_tls<std_tls, int>("std_tls: int");
|
||||
benchmark_tls<ipc_tls, int>("ipc_tls: int");
|
||||
benchmark_tls<std_tls, Str>("std_tls: Str");
|
||||
benchmark_tls<ipc_tls, Str>("ipc_tls: Str");
|
||||
}
|
||||
#endif
|
||||
@ -1,68 +0,0 @@
|
||||
#include <thread>
|
||||
#include <iostream>
|
||||
|
||||
#include "libipc/waiter.h"
|
||||
#include "test.h"
|
||||
|
||||
namespace {
|
||||
|
||||
TEST(Waiter, broadcast) {
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
ipc::detail::waiter waiter;
|
||||
std::thread ts[10];
|
||||
|
||||
int k = 0;
|
||||
for (auto& t : ts) {
|
||||
t = std::thread([&k] {
|
||||
ipc::detail::waiter waiter {"test-ipc-waiter"};
|
||||
EXPECT_TRUE(waiter.valid());
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
while (!waiter.wait_if([&k, &i] { return k == i; })) ;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
EXPECT_TRUE(waiter.open("test-ipc-waiter"));
|
||||
std::cout << "waiting for broadcast...\n";
|
||||
for (k = 1; k < 10; ++k) {
|
||||
std::cout << "broadcast: " << k << "\n";
|
||||
ASSERT_TRUE(waiter.broadcast());
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
for (auto& t : ts) t.join();
|
||||
std::cout << "quit... " << i << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Waiter, quit_waiting) {
|
||||
ipc::detail::waiter waiter;
|
||||
EXPECT_TRUE(waiter.open("test-ipc-waiter"));
|
||||
|
||||
std::thread t1 {
|
||||
[&waiter] {
|
||||
EXPECT_TRUE(waiter.wait_if([] { return true; }));
|
||||
}
|
||||
};
|
||||
|
||||
bool quit = false;
|
||||
std::thread t2 {
|
||||
[&quit] {
|
||||
ipc::detail::waiter waiter {"test-ipc-waiter"};
|
||||
EXPECT_TRUE(waiter.wait_if([&quit] { return !quit; }));
|
||||
}
|
||||
};
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
EXPECT_TRUE(waiter.quit_waiting());
|
||||
t1.join();
|
||||
ASSERT_TRUE(t2.joinable());
|
||||
|
||||
EXPECT_TRUE(waiter.open("test-ipc-waiter"));
|
||||
std::cout << "nofify quit...\n";
|
||||
quit = true;
|
||||
EXPECT_TRUE(waiter.notify());
|
||||
t2.join();
|
||||
std::cout << "quit... \n";
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
@ -1,123 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <thread> // std::thread
|
||||
#include <mutex> // std::mutex
|
||||
#include <condition_variable> // std::condition_variable
|
||||
#include <deque> // std::deque
|
||||
#include <functional> // std::function
|
||||
#include <utility> // std::forward, std::move
|
||||
#include <cstddef> // std::size_t
|
||||
#include <cassert> // assert
|
||||
|
||||
#include "capo/scope_guard.hpp"
|
||||
|
||||
namespace ipc_ut {
|
||||
|
||||
class thread_pool final {
|
||||
|
||||
std::deque<std::thread> workers_;
|
||||
std::deque<std::function<void()>> jobs_;
|
||||
|
||||
std::mutex lock_;
|
||||
std::condition_variable cv_jobs_;
|
||||
std::condition_variable cv_empty_;
|
||||
|
||||
std::size_t waiting_cnt_ = 0;
|
||||
bool quit_ = false;
|
||||
|
||||
static void proc(thread_pool * pool) {
|
||||
assert(pool != nullptr);
|
||||
std::function<void()> job;
|
||||
for (;;) {
|
||||
{
|
||||
std::unique_lock<std::mutex> guard { pool->lock_ };
|
||||
if (pool->quit_) return;
|
||||
if (pool->jobs_.empty()) {
|
||||
pool->waiting_cnt_ += 1;
|
||||
CAPO_SCOPE_GUARD_ = [pool] {
|
||||
pool->waiting_cnt_ -= 1;
|
||||
};
|
||||
|
||||
if (pool->waiting_cnt_ == pool->workers_.size()) {
|
||||
pool->cv_empty_.notify_all();
|
||||
}
|
||||
assert(pool->waiting_cnt_ <= pool->workers_.size());
|
||||
do {
|
||||
pool->cv_jobs_.wait(guard);
|
||||
if (pool->quit_) return;
|
||||
} while (pool->jobs_.empty());
|
||||
}
|
||||
assert(!pool->jobs_.empty());
|
||||
job = std::move(pool->jobs_.front());
|
||||
pool->jobs_.pop_front();
|
||||
}
|
||||
if (job) job();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
thread_pool() = default;
|
||||
|
||||
~thread_pool() {
|
||||
{
|
||||
std::lock_guard<std::mutex> guard { lock_ };
|
||||
static_cast<void>(guard);
|
||||
quit_ = true;
|
||||
}
|
||||
cv_jobs_.notify_all();
|
||||
cv_empty_.notify_all();
|
||||
for (auto & trd : workers_) trd.join();
|
||||
}
|
||||
|
||||
explicit thread_pool(std::size_t n) : thread_pool() {
|
||||
start(n);
|
||||
}
|
||||
|
||||
void start(std::size_t n) {
|
||||
std::unique_lock<std::mutex> guard { lock_ };
|
||||
if (n <= workers_.size()) return;
|
||||
for (std::size_t i = workers_.size(); i < n; ++i) {
|
||||
workers_.push_back(std::thread { &thread_pool::proc, this });
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t size() const noexcept {
|
||||
return workers_.size();
|
||||
}
|
||||
|
||||
std::size_t jobs_size() const noexcept {
|
||||
return jobs_.size();
|
||||
}
|
||||
|
||||
void wait_for_started() {
|
||||
std::unique_lock<std::mutex> guard { lock_ };
|
||||
if (quit_) return;
|
||||
while (!workers_.empty() && (waiting_cnt_ != workers_.size())) {
|
||||
cv_empty_.wait(guard);
|
||||
if (quit_) return;
|
||||
}
|
||||
}
|
||||
|
||||
void wait_for_done() {
|
||||
std::unique_lock<std::mutex> guard { lock_ };
|
||||
if (quit_) return;
|
||||
while (!jobs_.empty() || (waiting_cnt_ != workers_.size())) {
|
||||
assert(waiting_cnt_ <= workers_.size());
|
||||
cv_empty_.wait(guard);
|
||||
if (quit_) return;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
thread_pool & operator<<(F && job) {
|
||||
{
|
||||
std::lock_guard<std::mutex> guard { lock_ };
|
||||
static_cast<void>(guard);
|
||||
jobs_.emplace_back(std::forward<F>(job));
|
||||
}
|
||||
cv_jobs_.notify_one();
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ipc_ut
|
||||
Loading…
x
Reference in New Issue
Block a user