This commit is contained in:
jiegeaiai 2024-12-22 23:24:02 +08:00
parent bdf6b3f182
commit 3573b09efc
53 changed files with 5655 additions and 1 deletions

View File

@ -24,13 +24,16 @@ SET(
INCLUDE_DIRECTORIES( INCLUDE_DIRECTORIES(
${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/Ipc
${Thirdparty} ${Thirdparty}
${Thirdparty}/libcef ${Thirdparty}/libcef
${Thirdparty}/libipc
) )
link_directories( link_directories(
${Thirdparty}/libcef/lib/Debug ${Thirdparty}/libcef/lib/Debug
${Thirdparty}/libipc/lib
) )

44
human_render/Constant.h Normal file
View File

@ -0,0 +1,44 @@
#pragma once
#include <atomic>
using int8 = char;
using uint8 = unsigned char;
using int16 = short;
using uint16 = unsigned short;
using int32 = int;
using uint32 = unsigned int;
using ulong32 = unsigned long;
using int64 = long long;
using uint64 = unsigned long long;
using AtomicRefCount = std::atomic<int32>;
//
//#ifndef MAKEWORD
//#define MAKEWORD(a, b) ((uint16)(((uint8)(((uint64)(a)) & 0xff)) | ((uint16)((uint8)(((uint64)(b)) & 0xff))) << 8))
//#endif // !MAKEWORD
//
//#ifndef MAKELONG
//#define MAKELONG(a, b) ((long)(((uint16)(((uint64)(a)) & 0xffff)) | ((unsigned long)((uint16)(((uint64)(b)) & 0xffff))) << 16))
//#endif // !MAKELONG
//
//#ifndef LOWORD
//#define LOWORD(l) ((uint16)(((uint64)(l)) & 0xffff))
//#endif // !LOWORD
//
//#ifndef HIWORD
//#define HIWORD(l) ((uint16)((((uint64)(l)) >> 16) & 0xffff))
//#endif // !HIWORD
//
//#ifndef LOBYTE
//#define LOBYTE(w) ((uint8)(((uint64)(w)) & 0xff))
//#endif // !LOBYTE
//
//#ifndef HIBYTE
//#define HIBYTE(w) ((uint8)((((uint64)(w)) >> 8) & 0xff))
//#endif // !HIBYTE
#define NON_COPYABLE(class_name) \
class_name(const class_name&) = delete; \
class_name& operator=(const class_name&) = delete;

128
human_render/Ipc/Ipc.cpp Normal file
View File

@ -0,0 +1,128 @@
#include "Ipc/Ipc.h"
#include <functional>
#include <locale>
#include <codecvt>
#include "Ipc/ReadCallback.h"
std::shared_ptr<Ipc> Ipc::create(const char* reader_name, const char* writer_name) {
struct Creator : public Ipc {
Creator(const char* reader_name, const char* writer_name) : Ipc(reader_name, writer_name) {}
~Creator() override = default;
};
return std::make_shared<Creator>(reader_name, writer_name);
}
Ipc::Ipc(const char* reader_name, const char* writer_name) noexcept
: reader_name_(reader_name)
, writer_name_(writer_name) {
sender_ = std::make_unique<ipc::channel>(toUtf8(reader_name_).c_str(), ipc::sender);
fprintf(stderr, "%s, %s\n", reader_name, writer_name);
}
Ipc::~Ipc() {
stop();
}
bool Ipc::listen() {
if (!reciver_stop_.load(std::memory_order_acquire)) {
return false;
}
reciver_stop_.store(false);
std::weak_ptr<Ipc> wThis = shared_from_this();
reciver_thread_ = std::move(std::thread(std::bind(&Ipc::doReciver, this, wThis)));
return true;
}
void Ipc::stop() {
if (reciver_stop_.load(std::memory_order_acquire)) {
return;
}
reciver_stop_.store(true, std::memory_order_release);
sender_->disconnect();
receiver_->disconnect();
if (reciver_thread_.joinable()) {
reciver_thread_.join();
}
}
bool Ipc::send(const char* data, unsigned int size) {
if (!sender_) {
return false;
}
return sender_->send(data, size);
}
bool Ipc::isConnected() {
/* if (receiver_) {
return receiver_->is_connected();
}*/
return false;
}
void Ipc::reConnect() {
sender_->reconnect(ipc::sender);
if (receiver_) {
receiver_->disconnect();
receiver_->reconnect(ipc::receiver);
}
}
void Ipc::registReadCallback(const std::shared_ptr<IReaderCallback>& reader) {
std::lock_guard<std::mutex> lock(mutex_);
auto itor = std::find(reader_callbacks_.begin(), reader_callbacks_.end(), reader);
if (reader_callbacks_.end() != itor) {
return;
}
reader_callbacks_.emplace_back(reader);
}
void Ipc::unregistReadCallback(const std::shared_ptr<IReaderCallback>& reader) {
std::lock_guard<std::mutex> lock(mutex_);
auto itor = std::find(reader_callbacks_.begin(), reader_callbacks_.end(), reader);
if (reader_callbacks_.end() != itor) {
return;
}
reader_callbacks_.erase(itor);
}
void Ipc::doReciver(std::weak_ptr<Ipc> wThis) {
std::shared_ptr<Ipc> self(wThis.lock());
if (!self) {
return;
}
receiver_ = std::make_unique<ipc::channel>(writer_name_.c_str(), ipc::receiver);
while (!reciver_stop_.load(std::memory_order_acquire)) {
ipc::buff_t buffer = receiver_->recv(5);
if (buffer.empty()) {
continue;
}
const char* data = buffer.get<const char*>();
onReciveer(data, static_cast<unsigned int>(buffer.size()));
}
}
void Ipc::onReciveer(const char* buffer, unsigned int size) {
std::lock_guard<std::mutex> lock(mutex_);
for (auto& read : reader_callbacks_) {
read->onRead(buffer, size);
}
}
std::string Ipc::toUtf8(const std::string& str) {
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
std::wstring wide_str = converter.from_bytes(str);
return converter.to_bytes(wide_str);
}

50
human_render/Ipc/Ipc.h Normal file
View File

@ -0,0 +1,50 @@
#pragma once
#include <thread>
#include <memory>
#include <atomic>
#include <string>
#include <mutex>
#include <libipc/ipc.h>
class IReaderCallback;
class Ipc : public std::enable_shared_from_this<Ipc>{
public:
static std::shared_ptr<Ipc> create(const char* reader_name, const char* writer_name);
public:
virtual ~Ipc();
bool listen();
void stop();
bool send(const char* data, unsigned int size);
bool isConnected();
void reConnect();
void registReadCallback(const std::shared_ptr<IReaderCallback>& reader);
void unregistReadCallback(const std::shared_ptr<IReaderCallback>& reader);
protected:
Ipc(const char* reader_name, const char* writer_name) noexcept;
private:
void doReciver(std::weak_ptr<Ipc> wThis);
void onReciveer(const char* buffer, unsigned int size);
std::string toUtf8(const std::string& str);
private:
std::string reader_name_;
std::string writer_name_;
std::unique_ptr<ipc::channel> sender_;
std::unique_ptr<ipc::channel> receiver_;
std::thread reciver_thread_;
std::atomic_bool reciver_stop_{true};
std::mutex mutex_;
using ReaderCallbackList = std::vector<std::shared_ptr<IReaderCallback>>;
ReaderCallbackList reader_callbacks_;
};

View File

@ -0,0 +1,113 @@
#include "Ipc/IpcMoudle.h"
#include <iostream>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb/stb_image_write.h>
//#include "Core/Core.h"
#include "Ipc/ipclib.h"
//#include "ImageBuffer.h"
//#include "VHI/VHI.h"
//IpcMoudle* Singleton<IpcMoudle>::instance_ = nullptr;
void OnParseImageData(const char* data, size_t size) {
if (size < 13) { // Minimum size check: 1 byte identifier + 4 bytes width + 4 bytes height + 4 bytes bit depth
std::cerr << "Invalid data size" << std::endl;
return;
}
// Extract identifier
char identifier = data[0];
const char* buffer = data + 1;
/* if (0x01 == identifier) {
IpcMoudle::Get()->PushImage(buffer, size - 1);
} else if (0x02 == identifier) {
IpcMoudle::Get()->PushVoice(buffer, size - 1);
}
INFOLOG("identifier {}", static_cast<int32>(identifier));*/
}
bool IpcMoudle::Initialize() {
//if (!initialize("human_render", "human_product")) {
// ERRORLOG("ipc initialize failed");
// return false;
//}
//setReaderCallback([](const char* data, unsigned int size) {
// //INFOLOG("ipc recive data:{}", size);
// OnParseImageData(data, size);
// }
//);
//if (!listen()) {
// ERRORLOG("ipc listen failed");
// return false;
//}
////auto callback = [](const char* data, unsigned int size) {
//// //INFOLOG("ipc recive data:{}", size);
//// OnParseImageData(data, size);
////};
////zmqMoudle_ = std::make_unique<ZmqMoudle>(callback);
////zmqMoudle_->Start();
//lastHeartbeatTime_ = std::chrono::steady_clock::now();
return true;
}
void IpcMoudle::Uninitialize() {
//zmqMoudle_->Stop();
uninitialize();
}
bool IpcMoudle::Send(const char* data, unsigned int size) {
return send(data, size);
return false;
}
void IpcMoudle::OnFrame() {
auto now = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::seconds>(now - lastHeartbeatTime_);
if (duration.count() >= 2) {
/* constexpr char heartbeat[] = "heartbeat";
if (!send("heartbeat", sizeof(heartbeat) / sizeof(heartbeat[0]))) {
ERRORLOG("send heartbeat failed");
reConnect();
}*/
lastHeartbeatTime_ = now;
}
}
void IpcMoudle::PushImage(const char* data, uint32 size) {
// Extract width
int width;
std::memcpy(&width, data, sizeof(int));
// Extract height
int height;
std::memcpy(&height, data + 4, sizeof(int));
// Extract bit depth
int bit_depth;
std::memcpy(&bit_depth, data + 8, sizeof(int));
// Extract image bytes
const char* img_bytes = data + 12;
size_t img_size = size - 12;
//std::vector<uint8> img_data(img_size);
//memcpy(img_data.data(), img_bytes, img_size);
//ImageBuffer::Get()->PushImage(ImageBuffer::IBType::Human, std::move(img_data), width, height, bit_depth);
}
void IpcMoudle::PushVoice(const char* data, uint32 size) {
/* IAudioRender* audioRender = VHI::Get()->GetAudioRender();
if (nullptr == audioRender) {
return;
}
audioRender->Write(data, size);*/
}

View File

@ -0,0 +1,32 @@
#pragma once
#include <chrono>
//#include "Core/Singleton.h"
//#include "Core/Constant.h"
//#include "Ipc/ZmqMoudle.h"
#include "Constant.h"
class IpcMoudle {// : public Singleton<IpcMoudle> {
//NON_COPYABLE(IpcMoudle)
public:
IpcMoudle() = default;
virtual ~IpcMoudle() = default;
bool Initialize();
void Uninitialize();
bool Send(const char* data, uint32 size);
void OnFrame();
void PushImage(const char* data, uint32 size);
void PushVoice(const char* data, uint32 size);
private:
std::chrono::time_point<std::chrono::steady_clock> lastHeartbeatTime_;
//std::unique_ptr<ZmqMoudle> zmqMoudle_;
};

View File

@ -0,0 +1,9 @@
#pragma once
class IReaderCallback {
public:
virtual ~IReaderCallback() = default;
// read thread callback
virtual void onRead(const char* buffer, unsigned int size) = 0;
};

View File

@ -0,0 +1,62 @@
#include "Ipc/ZmqMoudle.h"
#if 0
#include <memory>
#include <assert.h>
#include <zmq.hpp>
ZmqMoudle::ZmqMoudle(ZmqMoudleCallback callback)
: callback_(callback){
}
void ZmqMoudle::Start() {
if (work_) {
return;
}
shouldExit_.store(false);
auto run = [this]() {
zmq::context_t context(1);
zmq::socket_t subscriber(context, ZMQ_SUB);
subscriber.connect("tcp://127.0.0.1:55661");
subscriber.setsockopt(ZMQ_SUBSCRIBE, "", 0);
bool isFirst = true;
while (!shouldExit_.load()) {
zmq::message_t message;
if (subscriber.recv(message, zmq::recv_flags::none)) {
if (isFirst) {
auto start = std::chrono::high_resolution_clock::now();
// 打印自 Epoch 以来的时间(以秒为单位)
auto duration = std::chrono::duration<double>(start.time_since_epoch());
int64_t time = duration.count();
time = 0;
isFirst = true;
}
const char* data = static_cast<const char*>(message.data());
if (nullptr != callback_) {
callback_(data, message.size());
}
} else {
::_sleep(1);
}
}
};
work_.reset(new std::thread(run));
}
void ZmqMoudle::Stop() {
if (!work_) {
return;
}
shouldExit_.store(true);
if (work_->joinable()) {
work_->join();
}
}
#endif

View File

@ -0,0 +1,22 @@
#pragma once
#if 0
#include <memory>
#include <functional>
#include <thread>
#include <atomic>
using ZmqMoudleCallback = std::function<void(const char* , unsigned int)>;
class ZmqMoudle {
public:
ZmqMoudle(ZmqMoudleCallback callback);
void Start();
void Stop();
private:
ZmqMoudleCallback callback_;
std::unique_ptr<std::thread> work_;
void* context_{ nullptr };
std::atomic<bool> shouldExit_;
};
#endif

20
human_render/Ipc/config.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#ifdef _IPC_EXPORT_
#ifdef WIN32
#define IPC_EXPORT __declspec( dllexport )
#endif // WIN32
#else
#ifdef WIN32
#define IPC_EXPORT __declspec( dllimport )
#endif // WIN32
#endif // _IPC_EXPORT_

View File

@ -0,0 +1,80 @@
#include "Ipc/ipclib.h"
#include <memory>
#include <assert.h>
#include "Ipc.h"
#include "ReadCallback.h"
class ReaderCallback : public IReaderCallback {
public:
ReaderCallback() = default;
~ReaderCallback() override = default;
void onRead(const char* buffer, unsigned int size) override {
//printf("%s", buffer);
if (nullptr != m_readerCallbackFunc) {
m_readerCallbackFunc(buffer, size);
}
}
void setCallback(ReaderCallbackFunc func) {
m_readerCallbackFunc = func;
}
private:
ReaderCallback(const ReaderCallback&) = delete;
ReaderCallback operator= (const ReaderCallback&) = delete;
private:
ReaderCallbackFunc m_readerCallbackFunc{nullptr};
};
std::shared_ptr<Ipc> g_ipc;
std::shared_ptr<ReaderCallback> g_readCallback;
bool __stdcall initialize(const char* sender_name, const char* receiver_name) {
assert(!g_ipc);
g_ipc = Ipc::create(sender_name, receiver_name);
return true;
}
void __stdcall uninitialize() {
assert(g_ipc);
g_ipc->stop();
if (g_readCallback) {
g_ipc->unregistReadCallback(g_readCallback);
g_readCallback.reset();
}
g_ipc.reset();
}
bool __stdcall listen() {
assert(g_ipc);
return g_ipc->listen();
}
bool __stdcall send(const char* data, unsigned int size) {
assert(g_ipc);
return g_ipc->send(data, size);
}
bool __stdcall setReaderCallback(ReaderCallbackFunc callback) {
assert(g_ipc);
if (!g_readCallback) {
g_readCallback = std::make_shared<ReaderCallback>();
g_ipc->registReadCallback(g_readCallback);
}
g_readCallback->setCallback(callback);
return true;
}
void __stdcall reConnect() {
assert(g_ipc);
return g_ipc->reConnect();
}
bool __stdcall isConnect() {
assert(g_ipc);
return g_ipc->isConnected();
}

25
human_render/Ipc/ipclib.h Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include "Ipc/config.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
typedef void(__stdcall *ReaderCallbackFunc)(const char* data, unsigned int size);
bool __stdcall initialize(const char* sender_name, const char* receiver_name);
void __stdcall uninitialize();
bool __stdcall listen();
bool __stdcall send(const char* data, unsigned int size);
bool __stdcall setReaderCallback(ReaderCallbackFunc callback);
void __stdcall reConnect();
bool __stdcall isConnect();
#ifdef __cplusplus
}
#endif // __cplusplus

View File

@ -0,0 +1,87 @@
#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

View File

@ -0,0 +1,67 @@
#pragma once
#include <cstddef>
#include <tuple>
#include <vector>
#include <type_traits>
#include "libipc/def.h"
namespace ipc {
class 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 bool operator==(buffer const & b1, buffer const & b2);
friend bool operator!=(buffer const & b1, buffer const & b2);
private:
class buffer_;
buffer_* p_;
};
} // namespace ipc

View File

@ -0,0 +1,141 @@
#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 Q, typename F>
bool push(Q* que, F&& f) {
return head_.push(que, std::forward<F>(f), block_);
}
template <typename Q, typename F>
bool force_push(Q* que, F&& f) {
return head_.force_push(que, std::forward<F>(f), block_);
}
template <typename Q, typename F, typename R>
bool pop(Q* que, cursor_t* cur, F&& f, R&& out) {
if (cur == nullptr) return false;
return head_.pop(que, *cur, std::forward<F>(f), std::forward<R>(out), block_);
}
};
} // namespace circ
} // namespace ipc

View File

@ -0,0 +1,109 @@
#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, bool = relat_trait<P>::is_broadcast>
class conn_head;
template <typename P>
class conn_head<P, true> : public conn_head_base {
public:
cc_t connect() noexcept {
for (unsigned k = 0;; ipc::yield(k)) {
cc_t curr = this->cc_.load(std::memory_order_acquire);
cc_t next = curr | (curr + 1); // find the first 0, and set it to 1.
if (next == 0) {
// connection-slot is full.
return 0;
}
if (this->cc_.compare_exchange_weak(curr, next, std::memory_order_release)) {
return next ^ curr; // return connected id
}
}
}
cc_t disconnect(cc_t cc_id) noexcept {
return this->cc_.fetch_and(~cc_id, std::memory_order_acq_rel) & ~cc_id;
}
std::size_t conn_count(std::memory_order order = std::memory_order_acquire) const noexcept {
cc_t cur = this->cc_.load(order);
cc_t cnt; // accumulates the total bits set in cc
for (cnt = 0; cur; ++cnt) cur &= cur - 1;
return cnt;
}
};
template <typename P>
class conn_head<P, false> : 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

View File

@ -0,0 +1,38 @@
#pragma once
#include <cstdint> // std::uint64_t
#include "libipc/def.h"
#include "libipc/mutex.h"
namespace ipc {
namespace sync {
class 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

View File

@ -0,0 +1,73 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <limits> // std::numeric_limits
#include <new>
#include <utility>
namespace ipc {
// 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;
// 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> {};
// the prefix tag of a channel
struct prefix {
char const *str;
};
} // namespace ipc

View File

@ -0,0 +1,778 @@
#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 <mutex>
#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 };
}
acc_t *cc_acc(ipc::string const &pref) {
static ipc::unordered_map<ipc::string, ipc::shm::handle> handles;
static std::mutex lock;
std::lock_guard<std::mutex> guard {lock};
auto it = handles.find(pref);
if (it == handles.end()) {
ipc::string shm_name {ipc::make_prefix(pref, {"CA_CONN__"})};
ipc::shm::handle h;
if (!h.acquire(shm_name.c_str(), sizeof(acc_t))) {
ipc::error("[cc_acc] acquire failed: %s\n", shm_name.c_str());
return nullptr;
}
it = handles.emplace(pref, std::move(h)).first;
}
return static_cast<acc_t *>(it->second.get());
}
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;
}
};
struct conn_info_head {
ipc::string prefix_;
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 * prefix, char const * name)
: prefix_{ipc::make_string(prefix)}
, name_ {ipc::make_string(name)}
, cc_id_ {} {}
void init() {
if (!cc_waiter_.valid()) cc_waiter_.open(ipc::make_prefix(prefix_, {"CC_CONN__", name_}).c_str());
if (!wt_waiter_.valid()) wt_waiter_.open(ipc::make_prefix(prefix_, {"WT_CONN__", name_}).c_str());
if (!rd_waiter_.valid()) rd_waiter_.open(ipc::make_prefix(prefix_, {"RD_CONN__", name_}).c_str());
if (!acc_h_.valid()) acc_h_.acquire(ipc::make_prefix(prefix_, {"AC_CONN__", name_}).c_str(), sizeof(acc_t));
if (cc_id_ != 0) {
return;
}
acc_t *pacc = cc_acc(prefix_);
if (pacc == nullptr) {
// Failed to obtain the global accumulator.
return;
}
cc_id_ = pacc->fetch_add(1, std::memory_order_relaxed) + 1;
if (cc_id_ == 0) {
// The identity cannot be 0.
cc_id_ = pacc->fetch_add(1, std::memory_order_relaxed) + 1;
}
}
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;
}
};
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::unordered_map<ipc::string, ipc::shm::handle> handles_;
std::mutex lock_;
static bool make_handle(ipc::shm::handle &h, ipc::string const &shm_name, std::size_t chunk_size) {
if (!h.valid() &&
!h.acquire( shm_name.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 false;
}
return true;
}
public:
chunk_info_t *get_info(conn_info_head *inf, std::size_t chunk_size) {
ipc::string pref {(inf == nullptr) ? ipc::string{} : inf->prefix_};
ipc::string shm_name {ipc::make_prefix(pref, {"CHUNK_INFO__", ipc::to_string(chunk_size)})};
ipc::shm::handle *h;
{
std::lock_guard<std::mutex> guard {lock_};
h = &(handles_[pref]);
if (!make_handle(*h, shm_name, chunk_size)) {
return nullptr;
}
}
auto *info = static_cast<chunk_info_t*>(h->get());
if (info == nullptr) {
ipc::error("[chunk_storages] chunk_shm.id_info_.get failed: chunk_size = %zd\n", chunk_size);
return nullptr;
}
return info;
}
};
using deleter_t = void (*)(chunk_handle_t*);
using chunk_handle_ptr_t = std::unique_ptr<chunk_handle_t, deleter_t>;
static ipc::map<std::size_t, chunk_handle_ptr_t> chunk_hs;
return chunk_hs;
}
chunk_info_t *chunk_storage_info(conn_info_head *inf, 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_ptr_t = std::decay_t<decltype(storages)>::value_type::second_type;
using chunk_handle_t = chunk_handle_ptr_t::element_type;
guard.unlock();
IPC_UNUSED_ std::lock_guard<ipc::rw_lock> guard {lock};
it = storages.emplace(chunk_size, chunk_handle_ptr_t{
ipc::mem::alloc<chunk_handle_t>(), [](chunk_handle_t *p) {
ipc::mem::destruct(p);
}}).first;
}
}
return it->second->get_info(inf, chunk_size);
}
std::pair<ipc::storage_id_t, void*> acquire_storage(conn_info_head *inf, std::size_t size, ipc::circ::cc_t conns) {
std::size_t chunk_size = calc_chunk_size(size);
auto info = chunk_storage_info(inf, 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, conn_info_head *inf, 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(inf, chunk_size);
if (info == nullptr) return nullptr;
return info->at(chunk_size, id)->data();
}
void release_storage(ipc::storage_id_t id, conn_info_head *inf, 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(inf, 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, conn_info_head *inf, 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(inf, 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(conn_info_head *inf, 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_),
inf, static_cast<std::size_t>(r_size));
}
return true;
}
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 * pref, char const * name)
: conn_info_head{pref, name} { init(); }
void init() {
conn_info_head::init();
if (!que_.valid()) {
que_.open(ipc::make_prefix(prefix_, {
"QU_CONN__",
ipc::to_string(DataSize), "__",
ipc::to_string(AlignSize), "__",
this->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 bool connect(ipc::handle_t * ph, ipc::prefix pref, char const * name, bool start_to_recv) {
assert(ph != nullptr);
if (*ph == nullptr) {
*ph = ipc::mem::alloc<conn_info_t>(pref.str, name);
}
return reconnect(ph, start_to_recv);
}
static bool connect(ipc::handle_t * ph, char const * name, bool start_to_recv) {
return connect(ph, {nullptr}, name, start_to_recv);
}
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;
}
info_of(*ph)->init();
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 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
conn_info_t *inf = info_of(h);
auto acc = inf->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)(inf, que, msg_id);
if (size > ipc::large_msg_limit) {
auto dat = acquire_storage(inf, 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)) {
ipc::log("force_push: msg_id = %zd, remain = %d, size = %zd\n", msg_id, remain, size);
if (!que->force_push(
[info](void* p) { return clear_message<typename queue_t::value_t>(info, p); },
info->cc_id_, msg_id, remain, data, size)) {
return false;
}
}
info->rd_waiter_.broadcast();
return true;
};
}, h, data, size);
}
static bool try_send(ipc::handle_t h, void const * data, std::size_t size, 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 {};
}
conn_info_t *inf = info_of(h);
auto& rc = inf->recv_cache();
for (;;) {
// pop a new message
typename queue_t::value_t msg {};
if (!wait_for(inf->rd_waiter_, [que, &msg] {
return !que->pop(msg);
}, tm)) {
// pop failed, just return.
return {};
}
inf->wt_waiter_.broadcast();
if ((inf->acc() != nullptr) && (msg.cc_id_ == inf->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, inf, msg_size);
if (buf != nullptr) {
struct recycle_t {
ipc::storage_id_t storage_id;
conn_info_t * inf;
ipc::circ::cc_t curr_conns;
ipc::circ::cc_t conn_id;
} *r_info = ipc::mem::alloc<recycle_t>(recycle_t{
buf_id,
inf,
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,
r_info->inf,
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>
ipc::handle_t chan_impl<Flag>::inited() {
ipc::detail::waiter::init();
return nullptr;
}
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>::connect(ipc::handle_t * ph, prefix pref, char const * name, unsigned mode) {
return detail_impl<policy_t<Flag>>::connect(ph, pref, 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>
bool chan_impl<Flag>::try_send(ipc::handle_t h, void const * data, std::size_t size, std::uint64_t tm) {
return detail_impl<policy_t<Flag>>::try_send(h, data, size, tm);
}
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

View File

@ -0,0 +1,199 @@
#pragma once
#include <string>
#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 chan_impl {
static ipc::handle_t inited();
static bool connect (ipc::handle_t * ph, char const * name, unsigned mode);
static bool connect (ipc::handle_t * ph, prefix, 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 bool try_send(ipc::handle_t h, void const * data, std::size_t size, std::uint64_t tm);
static buff_t try_recv(ipc::handle_t h);
};
template <typename Flag>
class chan_wrapper {
private:
using detail_t = chan_impl<Flag>;
ipc::handle_t h_ = detail_t::inited();
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(prefix pref, char const * name, unsigned mode = ipc::sender)
: connected_{this->connect(pref, 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);
}
bool connect(prefix pref, 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_, pref, 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);
}
/**
* If timeout, this function would call 'force_push' to send the data forcibly.
*/
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);
}
/**
* If timeout, this function would just return false.
*/
bool try_send(void const * data, std::size_t size, std::uint64_t tm = default_timeout) {
return detail_t::try_send(h_, data, size, tm);
}
bool try_send(buff_t const & buff, std::uint64_t tm = default_timeout) {
return this->try_send(buff.data(), buff.size(), tm);
}
bool try_send(std::string const & str, std::uint64_t tm = default_timeout) {
return this->try_send(str.c_str(), str.size() + 1, tm);
}
buff_t recv(std::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
*
* \note 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
*
* \note 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

View File

@ -0,0 +1,424 @@
#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

View File

@ -0,0 +1,121 @@
#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

View File

@ -0,0 +1,110 @@
#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 {};
}
/// \brief Check string validity.
constexpr bool is_valid_string(char const *str) noexcept {
return (str != nullptr) && (str[0] != '\0');
}
/// \brief Make a valid string.
inline ipc::string make_string(char const *str) {
return is_valid_string(str) ? ipc::string{str} : ipc::string{};
}
/// \brief Combine prefix from a list of strings.
inline ipc::string make_prefix(ipc::string prefix, std::initializer_list<ipc::string> args) {
prefix += "__IPC_SHM__";
for (auto const &txt: args) {
if (txt.empty()) continue;
prefix += txt;
}
return prefix;
}
} // namespace ipc

View File

@ -0,0 +1,327 @@
#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

View File

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

View File

@ -0,0 +1,136 @@
#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_

View File

@ -0,0 +1,3 @@
#include "libipc/platform/detail.h"
#include "libipc/platform/win/shm_win.cpp"

View File

@ -0,0 +1,119 @@
#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{name} + "_COND_SEM_").c_str())) {
return false;
}
auto finally_sem = ipc::guard([this] { sem_.close(); }); // close when failed
if (!lock_.open((std::string{name} + "_COND_LOCK_").c_str())) {
return false;
}
auto finally_lock = ipc::guard([this] { lock_.close(); }); // close when failed
if (!shm_.acquire((std::string{name} + "_COND_SHM_").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

View File

@ -0,0 +1,35 @@
#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

View File

@ -0,0 +1,98 @@
#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;
static void init() {}
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, detail::to_tchar(name).c_str());
if (h_ == NULL) {
ipc::error("fail CreateMutex[%lu]: %s\n", ::GetLastError(), name);
return false;
}
return true;
}
void close() noexcept {
if (!valid()) return;
::CloseHandle(h_);
h_ = NULL;
}
bool lock(std::uint64_t tm) noexcept {
DWORD ret, ms = (tm == invalid_value) ? INFINITE : static_cast<DWORD>(tm);
for(;;) {
switch ((ret = ::WaitForSingleObject(h_, ms))) {
case WAIT_OBJECT_0:
return true;
case WAIT_TIMEOUT:
return false;
case WAIT_ABANDONED:
ipc::log("fail WaitForSingleObject[%lu]: WAIT_ABANDONED, try again.\n", ::GetLastError());
if (!unlock()) {
return false;
}
break; // loop again
default:
ipc::error("fail WaitForSingleObject[%lu]: 0x%08X\n", ::GetLastError(), ret);
return false;
}
}
}
bool try_lock() noexcept(false) {
DWORD ret = ::WaitForSingleObject(h_, 0);
switch (ret) {
case WAIT_OBJECT_0:
return true;
case WAIT_TIMEOUT:
return false;
case WAIT_ABANDONED:
unlock();
IPC_FALLTHROUGH_;
default:
ipc::error("fail WaitForSingleObject[%lu]: 0x%08X\n", ::GetLastError(), ret);
throw std::system_error{static_cast<int>(ret), std::system_category()};
}
}
bool unlock() noexcept {
if (!::ReleaseMutex(h_)) {
ipc::error("fail ReleaseMutex[%lu]\n", ::GetLastError());
return false;
}
return true;
}
};
} // namespace sync
} // namespace detail
} // namespace ipc

View File

@ -0,0 +1,74 @@
#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,
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

View File

@ -0,0 +1,140 @@
#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 (!is_valid_string(name)) {
ipc::error("fail acquire: name is empty\n");
return nullptr;
}
HANDLE h;
auto fmt_name = ipc::detail::to_tchar(name);
// Opens a named file mapping object.
if (mode == open) {
h = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, fmt_name.c_str());
if (h == NULL) {
ipc::error("fail OpenFileMapping[%d]: %s\n", static_cast<int>(::GetLastError()), name);
return nullptr;
}
}
// 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());
DWORD err = ::GetLastError();
// 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) && (err == ERROR_ALREADY_EXISTS)) {
if (h != NULL) ::CloseHandle(h);
h = NULL;
}
if (h == NULL) {
ipc::error("fail CreateFileMapping[%d]: %s\n", static_cast<int>(err), 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 (!is_valid_string(name)) {
ipc::error("fail remove: name is empty\n");
return;
}
// Do Nothing.
}
} // namespace shm
} // namespace ipc

View File

@ -0,0 +1,74 @@
#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

View File

@ -0,0 +1,25 @@
#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

View File

@ -0,0 +1,17 @@
#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

View File

@ -0,0 +1,102 @@
#pragma once
#include <new>
#include <utility>
#include "libipc/def.h"
namespace ipc {
namespace mem {
class 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

View File

@ -0,0 +1,433 @@
#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 W, typename F, typename E>
bool push(W* /*wrapper*/, F&& f, E* elems) {
auto cur_wt = circ::index_of(wt_.load(std::memory_order_relaxed));
if (cur_wt == circ::index_of(rd_.load(std::memory_order_acquire) - 1)) {
return false; // full
}
std::forward<F>(f)(&(elems[cur_wt].data_));
wt_.fetch_add(1, std::memory_order_release);
return true;
}
/**
* In single-single-unicast, 'force_push' means 'no reader' or 'the only one reader is dead'.
* So we could just disconnect all connections of receiver, and return false.
*/
template <typename W, typename F, typename E>
bool force_push(W* wrapper, F&&, E*) {
wrapper->elems()->disconnect_receiver(~static_cast<circ::cc_t>(0u));
return false;
}
template <typename W, typename F, typename R, typename E>
bool pop(W* /*wrapper*/, circ::u2_t& /*cur*/, F&& f, R&& out, 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_));
std::forward<R>(out)(true);
rd_.fetch_add(1, std::memory_order_release);
return true;
}
};
template <>
struct prod_cons_impl<wr<relat::single, relat::multi , trans::unicast>>
: prod_cons_impl<wr<relat::single, relat::single, trans::unicast>> {
template <typename W, typename F, typename E>
bool force_push(W* wrapper, F&&, E*) {
wrapper->elems()->disconnect_receiver(1);
return false;
}
template <typename W, typename F, typename R,
template <std::size_t, std::size_t> class E, std::size_t DS, std::size_t AS>
bool pop(W* /*wrapper*/, circ::u2_t& /*cur*/, F&& f, R&& out, E<DS, AS>* elems) {
byte_t buff[DS];
for (unsigned k = 0;;) {
auto cur_rd = rd_.load(std::memory_order_relaxed);
if (circ::index_of(cur_rd) ==
circ::index_of(wt_.load(std::memory_order_acquire))) {
return false; // empty
}
std::memcpy(buff, &(elems[circ::index_of(cur_rd)].data_), sizeof(buff));
if (rd_.compare_exchange_weak(cur_rd, cur_rd + 1, std::memory_order_release)) {
std::forward<F>(f)(buff);
std::forward<R>(out)(true);
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 W, typename F, typename E>
bool push(W* /*wrapper*/, F&& f, E* elems) {
circ::u2_t cur_ct, nxt_ct;
for (unsigned k = 0;;) {
cur_ct = ct_.load(std::memory_order_relaxed);
if (circ::index_of(nxt_ct = cur_ct + 1) ==
circ::index_of(rd_.load(std::memory_order_acquire))) {
return false; // full
}
if (ct_.compare_exchange_weak(cur_ct, nxt_ct, std::memory_order_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 W, typename F, typename E>
bool force_push(W* wrapper, F&&, E*) {
wrapper->elems()->disconnect_receiver(1);
return false;
}
template <typename W, typename F, typename R,
template <std::size_t, std::size_t> class E, std::size_t DS, std::size_t AS>
bool pop(W* /*wrapper*/, circ::u2_t& /*cur*/, F&& f, R&& out, E<DS, AS>* elems) {
byte_t buff[DS];
for (unsigned k = 0;;) {
auto cur_rd = rd_.load(std::memory_order_relaxed);
auto cur_wt = wt_.load(std::memory_order_acquire);
auto id_rd = circ::index_of(cur_rd);
auto id_wt = circ::index_of(cur_wt);
if (id_rd == id_wt) {
auto* el = elems + id_wt;
auto cac_ct = el->f_ct_.load(std::memory_order_acquire);
if ((~cac_ct) != cur_wt) {
return false; // empty
}
if (el->f_ct_.compare_exchange_weak(cac_ct, 0, std::memory_order_relaxed)) {
wt_.store(cur_wt + 1, std::memory_order_release);
}
k = 0;
}
else {
std::memcpy(buff, &(elems[circ::index_of(cur_rd)].data_), sizeof(buff));
if (rd_.compare_exchange_weak(cur_rd, cur_rd + 1, std::memory_order_release)) {
std::forward<F>(f)(buff);
std::forward<R>(out)(true);
return true;
}
ipc::yield(k);
}
}
}
};
template <>
struct prod_cons_impl<wr<relat::single, relat::multi, trans::broadcast>> {
using rc_t = std::uint64_t;
enum : rc_t {
ep_mask = 0x00000000ffffffffull,
ep_incr = 0x0000000100000000ull
};
template <std::size_t DataSize, std::size_t AlignSize>
struct elem_t {
std::aligned_storage_t<DataSize, AlignSize> data_ {};
std::atomic<rc_t> rc_ { 0 }; // read-counter
};
alignas(cache_line_size) std::atomic<circ::u2_t> wt_; // write index
alignas(cache_line_size) rc_t epoch_ { 0 }; // only one writer
circ::u2_t cursor() const noexcept {
return wt_.load(std::memory_order_acquire);
}
template <typename W, typename F, typename E>
bool push(W* wrapper, F&& f, E* elems) {
E* el;
for (unsigned k = 0;;) {
circ::cc_t cc = wrapper->elems()->connections(std::memory_order_relaxed);
if (cc == 0) return false; // no reader
el = elems + circ::index_of(wt_.load(std::memory_order_relaxed));
// check all consumers have finished reading this element
auto cur_rc = el->rc_.load(std::memory_order_acquire);
circ::cc_t rem_cc = cur_rc & ep_mask;
if ((cc & rem_cc) && ((cur_rc & ~ep_mask) == epoch_)) {
return false; // has not finished yet
}
// consider rem_cc to be 0 here
if (el->rc_.compare_exchange_weak(
cur_rc, epoch_ | static_cast<rc_t>(cc), std::memory_order_release)) {
break;
}
ipc::yield(k);
}
std::forward<F>(f)(&(el->data_));
wt_.fetch_add(1, std::memory_order_release);
return true;
}
template <typename W, typename F, typename E>
bool force_push(W* wrapper, F&& f, E* elems) {
E* el;
epoch_ += ep_incr;
for (unsigned k = 0;;) {
circ::cc_t cc = wrapper->elems()->connections(std::memory_order_relaxed);
if (cc == 0) return false; // no reader
el = elems + circ::index_of(wt_.load(std::memory_order_relaxed));
// check all consumers have finished reading this element
auto cur_rc = el->rc_.load(std::memory_order_acquire);
circ::cc_t rem_cc = cur_rc & ep_mask;
if (cc & rem_cc) {
ipc::log("force_push: k = %u, cc = %u, rem_cc = %u\n", k, cc, rem_cc);
cc = wrapper->elems()->disconnect_receiver(rem_cc); // disconnect all invalid readers
if (cc == 0) return false; // no reader
}
// just compare & exchange
if (el->rc_.compare_exchange_weak(
cur_rc, epoch_ | static_cast<rc_t>(cc), std::memory_order_release)) {
break;
}
ipc::yield(k);
}
std::forward<F>(f)(&(el->data_));
wt_.fetch_add(1, std::memory_order_release);
return true;
}
template <typename W, typename F, typename R, typename E>
bool pop(W* wrapper, circ::u2_t& cur, F&& f, R&& out, E* elems) {
if (cur == cursor()) return false; // acquire
auto* el = elems + circ::index_of(cur++);
std::forward<F>(f)(&(el->data_));
for (unsigned k = 0;;) {
auto cur_rc = el->rc_.load(std::memory_order_acquire);
if ((cur_rc & ep_mask) == 0) {
std::forward<R>(out)(true);
return true;
}
auto nxt_rc = cur_rc & ~static_cast<rc_t>(wrapper->connected_id());
if (el->rc_.compare_exchange_weak(cur_rc, nxt_rc, std::memory_order_release)) {
std::forward<R>(out)((nxt_rc & ep_mask) == 0);
return true;
}
ipc::yield(k);
}
}
};
template <>
struct prod_cons_impl<wr<relat::multi, relat::multi, trans::broadcast>> {
using rc_t = std::uint64_t;
using flag_t = std::uint64_t;
enum : rc_t {
rc_mask = 0x00000000ffffffffull,
ep_mask = 0x00ffffffffffffffull,
ep_incr = 0x0100000000000000ull,
ic_mask = 0xff000000ffffffffull,
ic_incr = 0x0000000100000000ull
};
template <std::size_t DataSize, std::size_t AlignSize>
struct elem_t {
std::aligned_storage_t<DataSize, AlignSize> data_ {};
std::atomic<rc_t > rc_ { 0 }; // read-counter
std::atomic<flag_t> f_ct_ { 0 }; // commit flag
};
alignas(cache_line_size) std::atomic<circ::u2_t> ct_; // commit index
alignas(cache_line_size) std::atomic<rc_t> epoch_ { 0 };
circ::u2_t cursor() const noexcept {
return ct_.load(std::memory_order_acquire);
}
constexpr static rc_t inc_rc(rc_t rc) noexcept {
return (rc & ic_mask) | ((rc + ic_incr) & ~ic_mask);
}
constexpr static rc_t inc_mask(rc_t rc) noexcept {
return inc_rc(rc) & ~rc_mask;
}
template <typename W, typename F, typename E>
bool push(W* wrapper, F&& f, E* elems) {
E* el;
circ::u2_t cur_ct;
rc_t epoch = epoch_.load(std::memory_order_acquire);
for (unsigned k = 0;;) {
circ::cc_t cc = wrapper->elems()->connections(std::memory_order_relaxed);
if (cc == 0) return false; // no reader
el = elems + circ::index_of(cur_ct = ct_.load(std::memory_order_relaxed));
// check all consumers have finished reading this element
auto cur_rc = el->rc_.load(std::memory_order_relaxed);
circ::cc_t rem_cc = cur_rc & rc_mask;
if ((cc & rem_cc) && ((cur_rc & ~ep_mask) == epoch)) {
return false; // has not finished yet
}
else if (!rem_cc) {
auto cur_fl = el->f_ct_.load(std::memory_order_acquire);
if ((cur_fl != cur_ct) && cur_fl) {
return false; // full
}
}
// consider rem_cc to be 0 here
if (el->rc_.compare_exchange_weak(
cur_rc, inc_mask(epoch | (cur_rc & ep_mask)) | static_cast<rc_t>(cc), std::memory_order_relaxed) &&
epoch_.compare_exchange_weak(epoch, epoch, std::memory_order_acq_rel)) {
break;
}
ipc::yield(k);
}
// only one thread/process would touch here at one time
ct_.store(cur_ct + 1, std::memory_order_release);
std::forward<F>(f)(&(el->data_));
// set flag & try update wt
el->f_ct_.store(~static_cast<flag_t>(cur_ct), std::memory_order_release);
return true;
}
template <typename W, typename F, typename E>
bool force_push(W* wrapper, F&& f, E* elems) {
E* el;
circ::u2_t cur_ct;
rc_t epoch = epoch_.fetch_add(ep_incr, std::memory_order_release) + ep_incr;
for (unsigned k = 0;;) {
circ::cc_t cc = wrapper->elems()->connections(std::memory_order_relaxed);
if (cc == 0) return false; // no reader
el = elems + circ::index_of(cur_ct = ct_.load(std::memory_order_relaxed));
// check all consumers have finished reading this element
auto cur_rc = el->rc_.load(std::memory_order_acquire);
circ::cc_t rem_cc = cur_rc & rc_mask;
if (cc & rem_cc) {
ipc::log("force_push: k = %u, cc = %u, rem_cc = %u\n", k, cc, rem_cc);
cc = wrapper->elems()->disconnect_receiver(rem_cc); // disconnect all invalid readers
if (cc == 0) return false; // no reader
}
// just compare & exchange
if (el->rc_.compare_exchange_weak(
cur_rc, inc_mask(epoch | (cur_rc & ep_mask)) | static_cast<rc_t>(cc), std::memory_order_relaxed)) {
if (epoch == epoch_.load(std::memory_order_acquire)) {
break;
}
else if (push(wrapper, std::forward<F>(f), elems)) {
return true;
}
epoch = epoch_.fetch_add(ep_incr, std::memory_order_release) + ep_incr;
}
ipc::yield(k);
}
// only one thread/process would touch here at one time
ct_.store(cur_ct + 1, std::memory_order_release);
std::forward<F>(f)(&(el->data_));
// set flag & try update wt
el->f_ct_.store(~static_cast<flag_t>(cur_ct), std::memory_order_release);
return true;
}
template <typename W, typename F, typename R, typename E, std::size_t N>
bool pop(W* wrapper, circ::u2_t& cur, F&& f, R&& out, E(& elems)[N]) {
auto* el = elems + circ::index_of(cur);
auto cur_fl = el->f_ct_.load(std::memory_order_acquire);
if (cur_fl != ~static_cast<flag_t>(cur)) {
return false; // empty
}
++cur;
std::forward<F>(f)(&(el->data_));
for (unsigned k = 0;;) {
auto cur_rc = el->rc_.load(std::memory_order_acquire);
if ((cur_rc & rc_mask) == 0) {
std::forward<R>(out)(true);
el->f_ct_.store(cur + N - 1, std::memory_order_release);
return true;
}
auto nxt_rc = inc_rc(cur_rc) & ~static_cast<rc_t>(wrapper->connected_id());
bool last_one = false;
if ((last_one = (nxt_rc & rc_mask) == 0)) {
el->f_ct_.store(cur + N - 1, std::memory_order_release);
}
if (el->rc_.compare_exchange_weak(cur_rc, nxt_rc, std::memory_order_release)) {
std::forward<R>(out)(last_one);
return true;
}
ipc::yield(k);
}
}
};
} // namespace ipc

View File

@ -0,0 +1,223 @@
#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"
#include "libipc/memory/resource.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 (!is_valid_string(name)) {
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_ = queue_conn::template 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();
}
bool open(char const * name) noexcept {
base_t::close();
elems_ = queue_conn::template open<elems_t>(name);
return elems_ != nullptr;
}
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(this, [&](void* p) {
if (prep(p)) ::new (p) T(std::forward<P>(params)...);
});
}
template <typename T, typename F, typename... P>
bool force_push(F&& prep, P&&... params) {
if (elems_ == nullptr) return false;
return elems_->force_push(this, [&](void* p) {
if (prep(p)) ::new (p) T(std::forward<P>(params)...);
});
}
template <typename T, typename F>
bool pop(T& item, F&& out) {
if (elems_ == nullptr) {
return false;
}
return elems_->pop(this, &(this->cursor_), [&item](void* p) {
::new (&item) T(std::move(*static_cast<T*>(p)));
}, std::forward<F>(out));
}
};
} // 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)...);
}
template <typename... P>
bool force_push(P&&... params) {
return base_t::template force_push<T>(std::forward<P>(params)...);
}
bool pop(T& item) {
return base_t::pop(item, [](bool) {});
}
template <typename F>
bool pop(T& item, F&& out) {
return base_t::pop(item, std::forward<F>(out));
}
};
} // namespace ipc

View File

@ -0,0 +1,171 @@
#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

View File

@ -0,0 +1,36 @@
#pragma once
#include <cstdint> // std::uint64_t
#include "libipc/def.h"
namespace ipc {
namespace sync {
class 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

View File

@ -0,0 +1,113 @@
#include <string>
#include <utility>
#include "libipc/shm.h"
#include "libipc/utility/pimpl.h"
#include "libipc/utility/log.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) {
if (!is_valid_string(name)) {
ipc::error("fail acquire: name is empty\n");
return false;
}
if (size == 0) {
ipc::error("fail acquire: size is 0\n");
return false;
}
release();
impl(p_)->n_ = name;
impl(p_)->id_ = shm::acquire(name, 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

View File

@ -0,0 +1,58 @@
#pragma once
#include <cstddef>
#include <cstdint>
namespace ipc {
namespace shm {
using id_t = void*;
enum : unsigned {
create = 0x01,
open = 0x02
};
extern id_t acquire(char const * name, std::size_t size, unsigned mode = create | open);
extern void * get_mem(id_t id, std::size_t * size);
extern std::int32_t release(id_t id);
extern void remove (id_t id);
extern void remove (char const * name);
extern std::int32_t get_ref(id_t id);
extern void sub_ref(id_t id);
class 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

View File

@ -0,0 +1,77 @@
#include "libipc/condition.h"
#include "libipc/utility/pimpl.h"
#include "libipc/utility/log.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 {
if (!is_valid_string(name)) {
ipc::error("fail condition open: name is empty\n");
return false;
}
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

View File

@ -0,0 +1,77 @@
#include "libipc/mutex.h"
#include "libipc/utility/pimpl.h"
#include "libipc/utility/log.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 {
if (!is_valid_string(name)) {
ipc::error("fail mutex open: name is empty\n");
return false;
}
return impl(p_)->lock_.open(name);
}
void mutex::close() noexcept {
impl(p_)->lock_.close();
}
bool mutex::lock(std::uint64_t tm) noexcept {
return impl(p_)->lock_.lock(tm);
}
bool mutex::try_lock() noexcept(false) {
return impl(p_)->lock_.try_lock();
}
bool mutex::unlock() noexcept {
return impl(p_)->lock_.unlock();
}
} // namespace sync
} // namespace ipc

View File

@ -0,0 +1,71 @@
#include "libipc/semaphore.h"
#include "libipc/utility/pimpl.h"
#include "libipc/utility/log.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 {
if (!is_valid_string(name)) {
ipc::error("fail semaphore open: name is empty\n");
return false;
}
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

View File

@ -0,0 +1,22 @@
#include "libipc/waiter.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 detail {
void waiter::init() {
ipc::detail::sync::mutex::init();
}
} // namespace detail
} // namespace ipc

View File

@ -0,0 +1,29 @@
#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

View File

@ -0,0 +1,103 @@
#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

View File

@ -0,0 +1,39 @@
#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

View File

@ -0,0 +1,64 @@
#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

View File

@ -0,0 +1,64 @@
#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

View File

@ -0,0 +1,64 @@
#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

View File

@ -0,0 +1,87 @@
#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:
static void init();
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{name} + "_WAITER_COND_").c_str())) {
return false;
}
if (!lock_.open((std::string{name} + "_WAITER_LOCK_").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> barrier{lock_}; // barrier
}
return cond_.notify(lock_);
}
bool broadcast() noexcept {
{
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> barrier{lock_}; // barrier
}
return cond_.broadcast(lock_);
}
bool quit_waiting() {
quit_.store(true, std::memory_order_release);
return broadcast();
}
};
} // namespace detail
} // namespace ipc