Compare commits

..

No commits in common. "master" and "v1.0.0" have entirely different histories.

109 changed files with 2514 additions and 9497 deletions

1
.gitignore vendored
View File

@ -28,7 +28,6 @@ ui_*.h
*.jsc
Makefile*
*build-*
*build_*
# Qt unit tests
target_wrapper.*

31
.travis.yml Executable file
View File

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

View File

@ -1296,8 +1296,8 @@ static void StackLowerThanAddress(const void* ptr, bool* result) {
GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_
static bool StackGrowsDown() {
int dummy {};
bool result {};
int dummy;
bool result;
StackLowerThanAddress(&dummy, &result);
return result;
}

View File

@ -13,7 +13,7 @@ if(NOT MSVC)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
endif()
if (MSVC)
if (MSVC AND LIBIPC_USE_STATIC_CRT)
set(CompilerFlags
CMAKE_CXX_FLAGS
CMAKE_CXX_FLAGS_DEBUG
@ -22,17 +22,9 @@ if (MSVC)
CMAKE_C_FLAGS_DEBUG
CMAKE_C_FLAGS_RELEASE
)
if (LIBIPC_USE_STATIC_CRT)
foreach(CompilerFlag ${CompilerFlags})
string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}")
string(REPLACE "/MDd" "/MTd" ${CompilerFlag} "${${CompilerFlag}}")
endforeach()
else()
foreach(CompilerFlag ${CompilerFlags})
string(REPLACE "/MT" "/MD" ${CompilerFlag} "${${CompilerFlag}}")
string(REPLACE "/MTd" "/MDd" ${CompilerFlag} "${${CompilerFlag}}")
endforeach()
endif()
endif()
set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin)
@ -58,14 +50,6 @@ endif()
if (LIBIPC_BUILD_DEMOS)
add_subdirectory(demo/chat)
add_subdirectory(demo/msg_que)
add_subdirectory(demo/send_recv)
if (MSVC)
add_subdirectory(demo/win_service/service)
add_subdirectory(demo/win_service/client)
else()
add_subdirectory(demo/linux_service/service)
add_subdirectory(demo/linux_service/client)
endif()
endif()
install(

View File

@ -1,20 +1,19 @@
# cpp-ipc (libipc) - C++ IPC Library
# cpp-ipc(libipc) - C++ IPC Library
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/mutouyun/cpp-ipc/blob/master/LICENSE)
[![Build Status](https://github.com/mutouyun/cpp-ipc/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/mutouyun/cpp-ipc/actions)
[![Build status](https://ci.appveyor.com/api/projects/status/github/mutouyun/cpp-ipc?branch=master&svg=true)](https://ci.appveyor.com/project/mutouyun/cpp-ipc)
[![Vcpkg package](https://img.shields.io/badge/Vcpkg-package-blueviolet)](https://github.com/microsoft/vcpkg/tree/master/ports/cpp-ipc)
## A high-performance inter-process communication library using shared memory on Linux/Windows/FreeBSD.
A high-performance inter-process communication using shared memory on Linux/Windows.
使用共享内存的跨平台Linux/Windowsx86/x64/ARM高性能IPC通讯库。
* Compilers with C++17 support are recommended (msvc-2017/gcc-7/clang-4)
* No other dependencies except STL.
* Only lock-free or lightweight spin-lock is used.
* Circular array is used as the underline data structure.
* `ipc::route` supports single write and multiple read. `ipc::channel` supports multiple read and write. (**Note: currently, a channel supports up to 32 receivers, but there is no such a limit for the sender.**)
* Broadcasting is used by default, but user can choose any read/ write combinations.
* No long time blind wait. (Semaphore will be used after a certain number of retries.)
* [Vcpkg](https://github.com/microsoft/vcpkg/blob/master/README.md) way of installation is supported. E.g. `vcpkg install cpp-ipc`
* 推荐支持C++17的编译器msvc-2017/gcc-7/clang-4
* 除STL外无其他依赖
* 无锁lock-free或轻量级spin-lock
* 底层数据结构为循环数组circular array
* `ipc::route`支持单写多读,`ipc::channel`支持多写多读【**注意目前同一条通道最多支持32个receiversender无限制**】
* 默认采用广播模式收发数据,支持用户任意选择读写方案
* 不会长时间忙等(重试一定次数后会使用信号量进行等待),支持超时
## Usage
@ -30,7 +29,7 @@ See: [Wiki](https://github.com/mutouyun/cpp-ipc/wiki)
OS | Windows 7 Ultimate x64
Compiler | MSVC 2017 15.9.4
Unit & benchmark tests: [test](test)
UT & benchmark test function: [test](test)
Performance data: [performance.xlsx](performance.xlsx)
## Reference
@ -40,42 +39,3 @@ Performance data: [performance.xlsx](performance.xlsx)
* [Lock-Free 编程 | 匠心十年 - 博客园](http://www.cnblogs.com/gaochundong/p/lock_free_programming.html)
* [无锁队列的实现 | 酷 壳 - CoolShell](https://coolshell.cn/articles/8239.html)
* [Implementing Condition Variables with Semaphores](https://www.microsoft.com/en-us/research/wp-content/uploads/2004/12/ImplementingCVs.pdf)
------
## 使用共享内存的跨平台Linux/Windows/FreeBSDx86/x64/ARM高性能IPC通讯库
* 推荐支持C++17的编译器msvc-2017/gcc-7/clang-4
* 除STL外无其他依赖
* 无锁lock-free或轻量级spin-lock
* 底层数据结构为循环数组circular array
* `ipc::route`支持单写多读,`ipc::channel`支持多写多读【**注意目前同一条通道最多支持32个receiversender无限制**】
* 默认采用广播模式收发数据,支持用户任意选择读写方案
* 不会长时间忙等(重试一定次数后会使用信号量进行等待),支持超时
* 支持[Vcpkg](https://github.com/microsoft/vcpkg/blob/master/README_zh_CN.md)方式安装,如`vcpkg install cpp-ipc`
## 使用方法
详见:[Wiki](https://github.com/mutouyun/cpp-ipc/wiki)
## 性能
| 环境 | 值 |
| -------- | -------------------------------- |
| 设备 | 联想 ThinkPad T450 |
| CPU | 英特尔® Core™ i5-4300U @ 2.5 GHz |
| 内存 | 16 GB |
| 操作系统 | Windows 7 Ultimate x64 |
| 编译器 | MSVC 2017 15.9.4 |
单元测试和Benchmark测试: [test](test)
性能数据: [performance.xlsx](performance.xlsx)
## 参考
* [Lock-Free Data Structures | Dr Dobb's](http://www.drdobbs.com/lock-free-data-structures/184401865)
* [Yet another implementation of a lock-free circular array queue | CodeProject](https://www.codeproject.com/Articles/153898/Yet-another-implementation-of-a-lock-free-circular)
* [Lock-Free 编程 | 匠心十年 - 博客园](http://www.cnblogs.com/gaochundong/p/lock_free_programming.html)
* [无锁队列的实现 | 酷 壳 - CoolShell](https://coolshell.cn/articles/8239.html)
* [Implementing Condition Variables with Semaphores](https://www.microsoft.com/en-us/research/wp-content/uploads/2004/12/ImplementingCVs.pdf)

View File

@ -1,8 +0,0 @@
project(linux_client)
file(GLOB SRC_FILES ./*.cpp)
file(GLOB HEAD_FILES ./*.h)
add_executable(${PROJECT_NAME} ${SRC_FILES} ${HEAD_FILES})
target_link_libraries(${PROJECT_NAME} ipc)

View File

@ -1,28 +0,0 @@
/// \brief To create a basic command line program.
#include <stdio.h>
#include <thread>
#include <chrono>
#include "libipc/ipc.h"
int main(int argc, char *argv[]) {
printf("My Sample Client: Entry\n");
ipc::channel ipc_r{"service ipc r", ipc::receiver};
ipc::channel ipc_w{"service ipc w", ipc::sender};
while (1) {
auto msg = ipc_r.recv();
if (msg.empty()) {
printf("My Sample Client: message recv error\n");
return -1;
}
printf("My Sample Client: message recv: [%s]\n", (char const *)msg.data());
while (!ipc_w.send("Copy.")) {
printf("My Sample Client: message send error\n");
std::this_thread::sleep_for(std::chrono::seconds(1));
}
printf("My Sample Client: message send [Copy]\n");
}
printf("My Sample Client: Exit\n");
return 0;
}

View File

@ -1,8 +0,0 @@
project(linux_service)
file(GLOB SRC_FILES ./*.cpp)
file(GLOB HEAD_FILES ./*.h)
add_executable(${PROJECT_NAME} ${SRC_FILES} ${HEAD_FILES})
target_link_libraries(${PROJECT_NAME} ipc)

View File

@ -1,34 +0,0 @@
/// \brief To create a basic command line program.
#include <stdio.h>
#include <string>
#include <thread>
#include <chrono>
#include "libipc/ipc.h"
int main(int argc, char *argv[]) {
printf("My Sample Service: Main: Entry\n");
ipc::channel ipc_r{"service ipc r", ipc::sender};
ipc::channel ipc_w{"service ipc w", ipc::receiver};
while (1) {
if (!ipc_r.send("Hello, World!")) {
printf("My Sample Service: send failed.\n");
}
else {
printf("My Sample Service: send [Hello, World!]\n");
auto msg = ipc_w.recv(1000);
if (msg.empty()) {
printf("My Sample Service: recv error\n");
} else {
printf("%s\n", (std::string{"My Sample Service: recv ["} + msg.get<char const *>() + "]").c_str());
}
}
std::this_thread::sleep_for(std::chrono::seconds(3));
}
printf("My Sample Service: Main: Exit\n");
return 0;
}

View File

@ -20,8 +20,8 @@ constexpr char const mode_r__[] = "r";
constexpr std::size_t const min_sz = 128;
constexpr std::size_t const max_sz = 1024 * 16;
std::atomic<bool> is_quit__ {false};
std::atomic<std::size_t> size_counter__ {0};
std::atomic<bool> is_quit__{ false };
std::atomic<std::size_t> size_counter__{ 0 };
using msg_que_t = ipc::chan<ipc::relat::single, ipc::relat::multi, ipc::trans::broadcast>;
@ -127,10 +127,10 @@ int main(int argc, char ** argv) {
::signal(SIGHUP , exit);
#endif
std::string mode {argv[1]};
if (mode == mode_s__) {
if (std::string{ argv[1] } == mode_s__) {
do_send();
} else if (mode == mode_r__) {
}
else if (std::string{ argv[1] } == mode_r__) {
do_recv();
}
return 0;

View File

@ -1,11 +0,0 @@
project(send_recv)
include_directories(
${LIBIPC_PROJECT_DIR}/3rdparty)
file(GLOB SRC_FILES ./*.cpp)
file(GLOB HEAD_FILES ./*.h)
add_executable(${PROJECT_NAME} ${SRC_FILES} ${HEAD_FILES})
target_link_libraries(${PROJECT_NAME} ipc)

View File

@ -1,72 +0,0 @@
#include <signal.h>
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <atomic>
#include "libipc/ipc.h"
namespace {
std::atomic<bool> is_quit__ {false};
ipc::channel *ipc__ = nullptr;
void do_send(int size, int interval) {
ipc::channel ipc {"ipc", ipc::sender};
ipc__ = &ipc;
std::string buffer(size, 'A');
while (!is_quit__.load(std::memory_order_acquire)) {
std::cout << "send size: " << buffer.size() + 1 << "\n";
ipc.send(buffer, 0/*tm*/);
std::this_thread::sleep_for(std::chrono::milliseconds(interval));
}
}
void do_recv(int interval) {
ipc::channel ipc {"ipc", ipc::receiver};
ipc__ = &ipc;
while (!is_quit__.load(std::memory_order_acquire)) {
ipc::buff_t recv;
for (int k = 1; recv.empty(); ++k) {
std::cout << "recv waiting... " << k << "\n";
recv = ipc.recv(interval);
if (is_quit__.load(std::memory_order_acquire)) return;
}
std::cout << "recv size: " << recv.size() << "\n";
}
}
} // namespace
int main(int argc, char ** argv) {
if (argc < 3) return -1;
auto exit = [](int) {
is_quit__.store(true, std::memory_order_release);
if (ipc__ != nullptr) ipc__->disconnect();
};
::signal(SIGINT , exit);
::signal(SIGABRT , exit);
::signal(SIGSEGV , exit);
::signal(SIGTERM , exit);
#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || \
defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || \
defined(WINCE) || defined(_WIN32_WCE)
::signal(SIGBREAK, exit);
#else
::signal(SIGHUP , exit);
#endif
std::string mode {argv[1]};
if (mode == "send") {
if (argc < 4) return -1;
do_send(std::stoi(argv[2]) /*size*/,
std::stoi(argv[3]) /*interval*/);
} else if (mode == "recv") {
do_recv(std::stoi(argv[2]) /*interval*/);
}
return 0;
}

View File

@ -1,8 +0,0 @@
project(win_client)
file(GLOB SRC_FILES ./*.cpp)
file(GLOB HEAD_FILES ./*.h)
add_executable(${PROJECT_NAME} ${SRC_FILES} ${HEAD_FILES})
target_link_libraries(${PROJECT_NAME} ipc)

View File

@ -1,45 +0,0 @@
/// \brief To create a basic Windows command line program.
#if defined(__MINGW32__)
#include <windows.h>
#else
#include <Windows.h>
#endif
#include <tchar.h>
#include <stdio.h>
#include "libipc/ipc.h"
int _tmain (int argc, TCHAR *argv[]) {
_tprintf(_T("My Sample Client: Entry\n"));
ipc::channel ipc_r{ipc::prefix{"Global\\"}, "service ipc r", ipc::receiver};
ipc::channel ipc_w{ipc::prefix{"Global\\"}, "service ipc w", ipc::sender};
while (1) {
if (!ipc_r.reconnect(ipc::receiver)) {
Sleep(1000);
continue;
}
auto msg = ipc_r.recv();
if (msg.empty()) {
_tprintf(_T("My Sample Client: message recv error\n"));
ipc_r.disconnect();
continue;
}
printf("My Sample Client: message recv: [%s]\n", (char const *)msg.data());
for (;;) {
if (!ipc_w.reconnect(ipc::sender)) {
Sleep(1000);
continue;
}
if (ipc_w.send("Copy.")) {
break;
}
_tprintf(_T("My Sample Client: message send error\n"));
ipc_w.disconnect();
Sleep(1000);
}
_tprintf(_T("My Sample Client: message send [Copy]\n"));
}
_tprintf(_T("My Sample Client: Exit\n"));
return 0;
}

View File

@ -1,8 +0,0 @@
project(win_service)
file(GLOB SRC_FILES ./*.cpp)
file(GLOB HEAD_FILES ./*.h)
add_executable(${PROJECT_NAME} ${SRC_FILES} ${HEAD_FILES})
target_link_libraries(${PROJECT_NAME} ipc)

View File

@ -1,193 +0,0 @@
/// \brief To create a basic Windows Service in C++.
/// \see https://www.codeproject.com/Articles/499465/Simple-Windows-Service-in-Cplusplus
#if defined(__MINGW32__)
#include <windows.h>
#else
#include <Windows.h>
#endif
#include <tchar.h>
#include <string>
#include "libipc/ipc.h"
SERVICE_STATUS g_ServiceStatus = {0};
SERVICE_STATUS_HANDLE g_StatusHandle = NULL;
HANDLE g_ServiceStopEvent = INVALID_HANDLE_VALUE;
VOID WINAPI ServiceMain (DWORD argc, LPTSTR *argv);
VOID WINAPI ServiceCtrlHandler (DWORD);
DWORD WINAPI ServiceWorkerThread (LPVOID lpParam);
#define SERVICE_NAME _T("My Sample Service")
int _tmain (int argc, TCHAR *argv[]) {
OutputDebugString(_T("My Sample Service: Main: Entry"));
SERVICE_TABLE_ENTRY ServiceTable[] = {
{SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain},
{NULL, NULL}
};
if (StartServiceCtrlDispatcher (ServiceTable) == FALSE) {
OutputDebugString(_T("My Sample Service: Main: StartServiceCtrlDispatcher returned error"));
return GetLastError ();
}
OutputDebugString(_T("My Sample Service: Main: Exit"));
return 0;
}
VOID WINAPI ServiceMain (DWORD argc, LPTSTR *argv) {
DWORD Status = E_FAIL;
OutputDebugString(_T("My Sample Service: ServiceMain: Entry"));
g_StatusHandle = RegisterServiceCtrlHandler (SERVICE_NAME, ServiceCtrlHandler);
if (g_StatusHandle == NULL) {
OutputDebugString(_T("My Sample Service: ServiceMain: RegisterServiceCtrlHandler returned error"));
goto EXIT;
}
// Tell the service controller we are starting
ZeroMemory (&g_ServiceStatus, sizeof (g_ServiceStatus));
g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
g_ServiceStatus.dwControlsAccepted = 0;
g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
g_ServiceStatus.dwWin32ExitCode = 0;
g_ServiceStatus.dwServiceSpecificExitCode = 0;
g_ServiceStatus.dwCheckPoint = 0;
if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE) {
OutputDebugString(_T("My Sample Service: ServiceMain: SetServiceStatus returned error"));
}
/*
* Perform tasks neccesary to start the service here
*/
OutputDebugString(_T("My Sample Service: ServiceMain: Performing Service Start Operations"));
// Create stop event to wait on later.
g_ServiceStopEvent = CreateEvent (NULL, TRUE, FALSE, NULL);
if (g_ServiceStopEvent == NULL) {
OutputDebugString(_T("My Sample Service: ServiceMain: CreateEvent(g_ServiceStopEvent) returned error"));
g_ServiceStatus.dwControlsAccepted = 0;
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
g_ServiceStatus.dwWin32ExitCode = GetLastError();
g_ServiceStatus.dwCheckPoint = 1;
if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE) {
OutputDebugString(_T("My Sample Service: ServiceMain: SetServiceStatus returned error"));
}
goto EXIT;
}
// Tell the service controller we are started
g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
g_ServiceStatus.dwWin32ExitCode = 0;
g_ServiceStatus.dwCheckPoint = 0;
if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE) {
OutputDebugString(_T("My Sample Service: ServiceMain: SetServiceStatus returned error"));
}
// Start the thread that will perform the main task of the service
HANDLE hThread = CreateThread (NULL, 0, ServiceWorkerThread, NULL, 0, NULL);
OutputDebugString(_T("My Sample Service: ServiceMain: Waiting for Worker Thread to complete"));
// Wait until our worker thread exits effectively signaling that the service needs to stop
WaitForSingleObject (hThread, INFINITE);
OutputDebugString(_T("My Sample Service: ServiceMain: Worker Thread Stop Event signaled"));
/*
* Perform any cleanup tasks
*/
OutputDebugString(_T("My Sample Service: ServiceMain: Performing Cleanup Operations"));
CloseHandle (g_ServiceStopEvent);
g_ServiceStatus.dwControlsAccepted = 0;
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
g_ServiceStatus.dwWin32ExitCode = 0;
g_ServiceStatus.dwCheckPoint = 3;
if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE) {
OutputDebugString(_T("My Sample Service: ServiceMain: SetServiceStatus returned error"));
}
EXIT:
OutputDebugString(_T("My Sample Service: ServiceMain: Exit"));
return;
}
VOID WINAPI ServiceCtrlHandler (DWORD CtrlCode) {
OutputDebugString(_T("My Sample Service: ServiceCtrlHandler: Entry"));
switch (CtrlCode) {
case SERVICE_CONTROL_STOP :
OutputDebugString(_T("My Sample Service: ServiceCtrlHandler: SERVICE_CONTROL_STOP Request"));
if (g_ServiceStatus.dwCurrentState != SERVICE_RUNNING)
break;
/*
* Perform tasks neccesary to stop the service here
*/
g_ServiceStatus.dwControlsAccepted = 0;
g_ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
g_ServiceStatus.dwWin32ExitCode = 0;
g_ServiceStatus.dwCheckPoint = 4;
if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE) {
OutputDebugString(_T("My Sample Service: ServiceCtrlHandler: SetServiceStatus returned error"));
}
// This will signal the worker thread to start shutting down
SetEvent (g_ServiceStopEvent);
break;
default:
break;
}
OutputDebugString(_T("My Sample Service: ServiceCtrlHandler: Exit"));
}
DWORD WINAPI ServiceWorkerThread (LPVOID lpParam) {
OutputDebugString(_T("My Sample Service: ServiceWorkerThread: Entry"));
ipc::channel ipc_r{ipc::prefix{"Global\\"}, "service ipc r", ipc::sender};
ipc::channel ipc_w{ipc::prefix{"Global\\"}, "service ipc w", ipc::receiver};
// Periodically check if the service has been requested to stop
while (WaitForSingleObject(g_ServiceStopEvent, 0) != WAIT_OBJECT_0) {
/*
* Perform main service function here
*/
if (!ipc_r.send("Hello, World!")) {
OutputDebugString(_T("My Sample Service: send failed."));
}
else {
OutputDebugString(_T("My Sample Service: send [Hello, World!]"));
auto msg = ipc_w.recv(1000);
if (msg.empty()) {
OutputDebugString(_T("My Sample Service: recv error"));
} else {
OutputDebugStringA((std::string{"My Sample Service: recv ["} + msg.get<char const *>() + "]").c_str());
}
}
Sleep(3000);
}
OutputDebugString(_T("My Sample Service: ServiceWorkerThread: Exit"));
return ERROR_SUCCESS;
}

View File

@ -17,16 +17,14 @@ public:
buffer();
buffer(void* p, std::size_t s, destructor_t d);
// mem_to_free: pointer to be passed to destructor (if different from p)
// Use case: when p points into a larger allocated block that needs to be freed
buffer(void* p, std::size_t s, destructor_t d, void* mem_to_free);
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 (& data)[N])
explicit buffer(byte_t const (& data)[N])
: buffer(data, sizeof(data)) {
}
explicit buffer(char & c);
explicit buffer(char const & c);
buffer(buffer&& rhs);
~buffer();

View File

@ -1,42 +0,0 @@
#pragma once
#include <cstdint> // std::uint64_t
#include "libipc/export.h"
#include "libipc/def.h"
#include "libipc/mutex.h"
namespace ipc {
namespace sync {
class IPC_EXPORT condition {
condition(condition const &) = delete;
condition &operator=(condition const &) = delete;
public:
condition();
explicit condition(char const *name);
~condition();
void const *native() const noexcept;
void *native() noexcept;
bool valid() const noexcept;
bool open(char const *name) noexcept;
void close() noexcept;
void clear() noexcept;
static void clear_storage(char const * name) 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

@ -25,16 +25,13 @@ using uint_t = typename uint<N>::type;
// constants
enum : std::uint32_t {
invalid_value = (std::numeric_limits<std::uint32_t>::max)(),
default_timeout = 100, // ms
};
enum : std::size_t {
invalid_value = (std::numeric_limits<std::size_t>::max)(),
data_length = 64,
large_msg_limit = data_length,
large_msg_align = 1024,
large_msg_cache = 32,
default_timeout = 100 // ms
};
enum class relat { // multiplicity of the relationship
@ -65,9 +62,4 @@ struct relat_trait<wr<Rp, Rc, Ts>> {
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

@ -19,31 +19,20 @@ enum : unsigned {
template <typename Flag>
struct IPC_EXPORT chan_impl {
static ipc::handle_t init_first();
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);
// Release memory without waiting for the connection to disconnect.
static void release(ipc::handle_t h) noexcept;
static std::size_t recv_count(ipc::handle_t h);
static bool wait_for_recv(ipc::handle_t h, std::size_t r_count, std::size_t tm);
// Force cleanup of all shared memory storage that handles depend on.
static void clear(ipc::handle_t h) noexcept;
static void clear_storage(char const * name) noexcept;
static void clear_storage(prefix, char const * name) noexcept;
static bool send(ipc::handle_t h, void const * data, std::size_t size, std::size_t tm);
static buff_t recv(ipc::handle_t h, std::size_t tm);
static 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 bool try_send(ipc::handle_t h, void const * data, std::size_t size, std::size_t tm);
static buff_t try_recv(ipc::handle_t h);
};
@ -52,7 +41,7 @@ class chan_wrapper {
private:
using detail_t = chan_impl<Flag>;
ipc::handle_t h_ = detail_t::init_first();
ipc::handle_t h_ = nullptr;
unsigned mode_ = ipc::sender;
bool connected_ = false;
@ -63,10 +52,6 @@ public:
: 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);
@ -91,28 +76,6 @@ public:
return detail_t::name(h_);
}
// Release memory without waiting for the connection to disconnect.
void release() noexcept {
detail_t::release(h_);
h_ = nullptr;
}
// Clear shared memory files under opened handle.
void clear() noexcept {
detail_t::clear(h_);
h_ = nullptr;
}
// Clear shared memory files under a specific name.
static void clear_storage(char const * name) noexcept {
detail_t::clear_storage(name);
}
// Clear shared memory files under a specific name with a prefix.
static void clear_storage(prefix pref, char const * name) noexcept {
detail_t::clear_storage(pref, name);
}
ipc::handle_t handle() const noexcept {
return h_;
}
@ -137,11 +100,6 @@ public:
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.
@ -162,41 +120,41 @@ public:
return detail_t::recv_count(h_);
}
bool wait_for_recv(std::size_t r_count, std::uint64_t tm = invalid_value) const {
bool wait_for_recv(std::size_t r_count, std::size_t tm = invalid_value) const {
return detail_t::wait_for_recv(h_, r_count, tm);
}
static bool wait_for_recv(char const * name, std::size_t r_count, std::uint64_t tm = invalid_value) {
static bool wait_for_recv(char const * name, std::size_t r_count, std::size_t tm = invalid_value) {
return chan_wrapper(name).wait_for_recv(r_count, tm);
}
/**
* 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) {
bool send(void const * data, std::size_t size, std::size_t tm = default_timeout) {
return detail_t::send(h_, data, size, tm);
}
bool send(buff_t const & buff, std::uint64_t tm = default_timeout) {
bool send(buff_t const & buff, std::size_t tm = default_timeout) {
return this->send(buff.data(), buff.size(), tm);
}
bool send(std::string const & str, std::uint64_t tm = default_timeout) {
bool send(std::string const & str, std::size_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) {
bool try_send(void const * data, std::size_t size, std::size_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) {
bool try_send(buff_t const & buff, std::size_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) {
bool try_send(std::string const & str, std::size_t tm = default_timeout) {
return this->try_send(str.c_str(), str.size() + 1, tm);
}
buff_t recv(std::uint64_t tm = invalid_value) {
buff_t recv(std::size_t tm = invalid_value) {
return detail_t::recv(h_, tm);
}
@ -209,22 +167,26 @@ template <relat Rp, relat Rc, trans Ts>
using chan = chan_wrapper<ipc::wr<Rp, Rc, Ts>>;
/**
* \class route
* class route
*
* \note You could use one producer/server/sender for sending messages to a route,
* You could use one producer/server/sender for sending messages to a route,
* then all the consumers/clients/receivers which are receiving with this route,
* would receive your sent messages.
* A route could only be used in 1 to N (one producer/writer to multi consumers/readers).
*
* 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
* class channel
*
* \note You could use multi producers/writers for sending messages to a channel,
* You could use multi producers/writers for sending messages to a channel,
* then all the consumers/readers which are receiving with this channel,
* would receive your sent messages.
*/
using channel = chan<relat::multi, relat::multi, trans::broadcast>;
} // namespace ipc

View File

@ -1,42 +0,0 @@
#pragma once
#include <cstdint> // std::uint64_t
#include <system_error>
#include "libipc/export.h"
#include "libipc/def.h"
namespace ipc {
namespace sync {
class IPC_EXPORT mutex {
mutex(mutex const &) = delete;
mutex &operator=(mutex const &) = delete;
public:
mutex();
explicit mutex(char const *name);
~mutex();
void const *native() const noexcept;
void *native() noexcept;
bool valid() const noexcept;
bool open(char const *name) noexcept;
void close() noexcept;
void clear() noexcept;
static void clear_storage(char const * name) 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

@ -11,8 +11,8 @@ namespace mem {
class IPC_EXPORT pool_alloc {
public:
static void* alloc(std::size_t size) noexcept;
static void free (void* p, std::size_t size) noexcept;
static void* alloc(std::size_t size);
static void free (void* p, std::size_t size);
};
////////////////////////////////////////////////////////////////
@ -94,7 +94,6 @@ inline void free(void* p, std::size_t size) {
template <typename T>
void free(T* p) {
if (p == nullptr) return;
destruct(p);
pool_alloc::free(p, sizeof(T));
}

View File

@ -6,7 +6,6 @@
#include <limits>
#include <type_traits>
#include <utility>
#include <cstdint>
////////////////////////////////////////////////////////////////
/// Gives hint to processor that improves performance of spin-wait loops.
@ -73,7 +72,7 @@ inline void yield(K& k) noexcept {
++k;
}
template <std::size_t N = 32, typename K, typename F>
template <std::size_t N = 4096, typename K, typename F>
inline void sleep(K& k, F&& f) {
if (k < static_cast<K>(N)) {
std::this_thread::yield();
@ -85,7 +84,7 @@ inline void sleep(K& k, F&& f) {
++k;
}
template <std::size_t N = 32, typename K>
template <std::size_t N = 4096, typename K>
inline void sleep(K& k) {
sleep<N>(k, [] {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
@ -99,7 +98,7 @@ inline void sleep(K& k) {
namespace ipc {
class spin_lock {
std::atomic<std::uint32_t> lc_ { 0 };
std::atomic<unsigned> lc_ { 0 };
public:
void lock(void) noexcept {
@ -114,13 +113,13 @@ public:
};
class rw_lock {
using lc_ui_t = std::uint32_t;
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 ...
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:

View File

@ -1,40 +0,0 @@
#pragma once
#include <cstdint> // std::uint64_t
#include "libipc/export.h"
#include "libipc/def.h"
namespace ipc {
namespace sync {
class IPC_EXPORT semaphore {
semaphore(semaphore const &) = delete;
semaphore &operator=(semaphore const &) = delete;
public:
semaphore();
explicit semaphore(char const *name, std::uint32_t count = 0);
~semaphore();
void const *native() const noexcept;
void *native() noexcept;
bool valid() const noexcept;
bool open(char const *name, std::uint32_t count = 0) noexcept;
void close() noexcept;
void clear() noexcept;
static void clear_storage(char const * name) 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

@ -1,7 +1,6 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include "libipc/export.h"
@ -17,34 +16,9 @@ enum : unsigned {
IPC_EXPORT id_t acquire(char const * name, std::size_t size, unsigned mode = create | open);
IPC_EXPORT void * get_mem(id_t id, std::size_t * size);
// Release shared memory resource and clean up disk file if reference count reaches zero.
// This function decrements the reference counter. When the counter reaches zero, it:
// 1. Unmaps the shared memory region
// 2. Removes the backing file from disk (shm_unlink on POSIX)
// 3. Frees the id structure
// After calling this function, the id becomes invalid and must not be used again.
// Returns: The reference count before decrement, or -1 on error.
IPC_EXPORT std::int32_t release(id_t id) noexcept;
// Release shared memory resource and force cleanup of disk file.
// This function calls release(id) internally, then unconditionally attempts to
// remove the backing file. WARNING: Do NOT call this after release(id) on the
// same id, as the id is already freed by release(). Use this function alone,
// not in combination with release().
// Typical use case: Force cleanup when you want to ensure the disk file is removed
// regardless of reference count state.
IPC_EXPORT void remove (id_t id) noexcept;
// Remove shared memory backing file by name.
// This function only removes the disk file and does not affect any active memory
// mappings or id structures. Use this for cleanup of orphaned files or for explicit
// file removal without affecting runtime resources.
// Safe to call at any time, even if shared memory is still in use elsewhere.
IPC_EXPORT void remove (char const * name) noexcept;
IPC_EXPORT std::int32_t get_ref(id_t id);
IPC_EXPORT void sub_ref(id_t id);
IPC_EXPORT void release(id_t id);
IPC_EXPORT void remove (id_t id);
IPC_EXPORT void remove (char const * name);
class IPC_EXPORT handle {
public:
@ -57,19 +31,12 @@ public:
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 valid() const;
std::size_t size () const;
char const * name () const;
bool acquire(char const * name, std::size_t size, unsigned mode = create | open);
std::int32_t release();
// Clean the handle file.
void clear() noexcept;
static void clear_storage(char const * name) noexcept;
void release();
void* get() const;

93
include/libipc/waiter.h Executable file
View File

@ -0,0 +1,93 @@
#pragma once
#include "libipc/export.h"
#include "libipc/def.h"
namespace ipc {
class condition;
class IPC_EXPORT mutex {
public:
mutex();
explicit mutex(char const * name);
mutex(mutex&& rhs);
~mutex();
static void remove(char const * name);
void swap(mutex& rhs);
mutex& operator=(mutex rhs);
bool valid() const;
char const * name () const;
bool open (char const * name);
void close();
bool lock ();
bool unlock();
private:
class mutex_;
mutex_* p_;
friend class condition;
};
class IPC_EXPORT semaphore {
public:
semaphore();
explicit semaphore(char const * name);
semaphore(semaphore&& rhs);
~semaphore();
static void remove(char const * name);
void swap(semaphore& rhs);
semaphore& operator=(semaphore rhs);
bool valid() const;
char const * name () const;
bool open (char const * name, long count = 0);
void close();
bool wait(std::size_t tm = invalid_value);
bool post(long count = 1);
private:
class semaphore_;
semaphore_* p_;
};
class IPC_EXPORT condition {
public:
condition();
explicit condition(char const * name);
condition(condition&& rhs);
~condition();
static void remove(char const * name);
void swap(condition& rhs);
condition& operator=(condition rhs);
bool valid() const;
char const * name () const;
bool open (char const * name);
void close();
bool wait(mutex&, std::size_t tm = invalid_value);
bool notify();
bool broadcast();
private:
class condition_;
condition_* p_;
};
} // namespace ipc

Binary file not shown.

View File

@ -1,10 +1,11 @@
project(ipc)
set (PACKAGE_VERSION 1.3.0)
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc SRC_FILES)
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/sync SRC_FILES)
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/platform SRC_FILES)
if(UNIX)
file(GLOB SRC_FILES ${LIBIPC_PROJECT_DIR}/src/libipc/platform/*_linux.cpp)
else()
file(GLOB SRC_FILES ${LIBIPC_PROJECT_DIR}/src/libipc/platform/*_win.cpp)
endif()
aux_source_directory(${LIBIPC_PROJECT_DIR}/src SRC_FILES)
file(GLOB HEAD_FILES
${LIBIPC_PROJECT_DIR}/include/libipc/*.h
@ -31,51 +32,28 @@ set_target_properties(${PROJECT_NAME}
PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" )
# set version
set_target_properties(${PROJECT_NAME}
PROPERTIES
VERSION ${PACKAGE_VERSION}
SOVERSION 3)
VERSION 1.0.0
SOVERSION 1)
target_include_directories(${PROJECT_NAME}
PUBLIC
"$<BUILD_INTERFACE:${LIBIPC_PROJECT_DIR}/include>"
"$<INSTALL_INTERFACE:include>"
PUBLIC ${LIBIPC_PROJECT_DIR}/include
PRIVATE ${LIBIPC_PROJECT_DIR}/src
$<$<BOOL:UNIX>:${LIBIPC_PROJECT_DIR}/src/libipc/platform/linux>)
)
if(NOT MSVC)
target_link_libraries(${PROJECT_NAME} PUBLIC
$<$<NOT:$<STREQUAL:${CMAKE_SYSTEM_NAME},QNX>>:pthread>
$<$<NOT:$<OR:$<STREQUAL:${CMAKE_SYSTEM_NAME},Windows>,$<STREQUAL:${CMAKE_SYSTEM_NAME},QNX>>>:rt>)
pthread
$<$<NOT:$<STREQUAL:${CMAKE_SYSTEM_NAME},Windows>>:rt>)
endif()
install(
TARGETS ${PROJECT_NAME}
EXPORT cpp-ipc-targets
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
install(EXPORT cpp-ipc-targets
FILE cpp-ipc-targets.cmake
NAMESPACE cpp-ipc::
DESTINATION share/cpp-ipc
ARCHIVE DESTINATION lib
)
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/cpp-ipc-config.cmake.in"
[[include(CMakeFindDependencyMacro)
include("${CMAKE_CURRENT_LIST_DIR}/cpp-ipc-targets.cmake")
]])
configure_file("${CMAKE_CURRENT_BINARY_DIR}/cpp-ipc-config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cpp-ipc-config.cmake" @ONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cpp-ipc-config.cmake DESTINATION share/cpp-ipc)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
cppIpcConfigVersion.cmake
VERSION ${PACKAGE_VERSION}
COMPATIBILITY AnyNewerVersion
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cppIpcConfigVersion.cmake DESTINATION share/cpp-ipc)

View File

@ -38,16 +38,16 @@ 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* mem_to_free)
: p_(p_->make(p, s, d, mem_to_free)) {
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 & c)
: buffer(&c, 1) {
buffer::buffer(char const & c)
: buffer(const_cast<char*>(&c), 1) {
}
buffer::buffer(buffer&& rhs)

View File

@ -9,7 +9,6 @@
#include <vector>
#include <array>
#include <cassert>
#include <mutex>
#include "libipc/ipc.h"
#include "libipc/def.h"
@ -18,7 +17,6 @@
#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"
@ -26,7 +24,10 @@
#include "libipc/utility/utility.h"
#include "libipc/memory/resource.h"
#include "libipc/platform/detail.h"
#include "libipc/platform/waiter_wrapper.h"
#include "libipc/circ/elem_array.h"
namespace {
@ -70,23 +71,6 @@ ipc::buff_t make_cache(T& data, std::size_t 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_;
@ -103,70 +87,10 @@ struct cache_t {
}
};
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 clear() noexcept {
cc_waiter_.clear();
wt_waiter_.clear();
rd_waiter_.clear();
acc_h_.clear();
}
static void clear_storage(char const * prefix, char const * name) noexcept {
auto p = ipc::make_string(prefix);
auto n = ipc::make_string(name);
ipc::detail::waiter::clear_storage(ipc::make_prefix(p, {"CC_CONN__", n}).c_str());
ipc::detail::waiter::clear_storage(ipc::make_prefix(p, {"WT_CONN__", n}).c_str());
ipc::detail::waiter::clear_storage(ipc::make_prefix(p, {"RD_CONN__", n}).c_str());
ipc::shm::handle::clear_storage(ipc::make_prefix(p, {"AC_CONN__", n}).c_str());
}
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;
}
};
auto cc_acc() {
static ipc::shm::handle acc_h("__CA_CONN__", sizeof(acc_t));
return static_cast<acc_t*>(acc_h.get());
}
IPC_CONSTEXPR_ std::size_t align_chunk_size(std::size_t size) noexcept {
return (((size - 1) / ipc::large_msg_align) + 1) * ipc::large_msg_align;
@ -208,32 +132,17 @@ struct chunk_info_t {
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;
}
ipc::shm::handle handle_;
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)) {
chunk_info_t *get_info(std::size_t chunk_size) {
if (!handle_.valid() &&
!handle_.acquire( ("__CHUNK_INFO__" + ipc::to_string(chunk_size)).c_str(),
sizeof(chunk_info_t) + chunk_info_t::chunks_mem_size(chunk_size) )) {
ipc::error("[chunk_storages] chunk_shm.id_info_.acquire failed: chunk_size = %zd\n", chunk_size);
return nullptr;
}
}
auto *info = static_cast<chunk_info_t*>(h->get());
auto info = static_cast<chunk_info_t*>(handle_.get());
if (info == nullptr) {
ipc::error("[chunk_storages] chunk_shm.id_info_.get failed: chunk_size = %zd\n", chunk_size);
return nullptr;
@ -241,35 +150,29 @@ auto& chunk_storages() {
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;
static ipc::map<std::size_t, chunk_handle_t> chunk_hs;
return chunk_hs;
}
chunk_info_t *chunk_storage_info(conn_info_head *inf, std::size_t chunk_size) {
chunk_info_t *chunk_storage_info(std::size_t chunk_size) {
auto &storages = chunk_storages();
std::decay_t<decltype(storages)>::iterator it;
{
static ipc::rw_lock lock;
IPC_UNUSED_ std::shared_lock<ipc::rw_lock> guard {lock};
if ((it = storages.find(chunk_size)) == storages.end()) {
using chunk_handle_ptr_t = std::decay_t<decltype(storages)>::value_type::second_type;
using chunk_handle_t = chunk_handle_ptr_t::element_type;
using chunk_handle_t = std::decay_t<decltype(storages)>::value_type::second_type;
guard.unlock();
IPC_UNUSED_ std::lock_guard<ipc::rw_lock> guard {lock};
it = storages.emplace(chunk_size, chunk_handle_ptr_t{
ipc::mem::alloc<chunk_handle_t>(), [](chunk_handle_t *p) {
ipc::mem::destruct(p);
}}).first;
it = storages.emplace(chunk_size, chunk_handle_t{}).first;
}
}
return it->second->get_info(inf, chunk_size);
return it->second.get_info(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::pair<ipc::storage_id_t, void*> acquire_storage(std::size_t size, ipc::circ::cc_t conns) {
std::size_t chunk_size = calc_chunk_size(size);
auto info = chunk_storage_info(inf, chunk_size);
auto info = chunk_storage_info(chunk_size);
if (info == nullptr) return {};
info->lock_.lock();
@ -284,24 +187,24 @@ std::pair<ipc::storage_id_t, void*> acquire_storage(conn_info_head *inf, std::si
return { id, chunk->data() };
}
void *find_storage(ipc::storage_id_t id, conn_info_head *inf, std::size_t size) {
void *find_storage(ipc::storage_id_t id, std::size_t size) {
if (id < 0) {
ipc::error("[find_storage] id is invalid: id = %ld, size = %zd\n", (long)id, size);
return nullptr;
}
std::size_t chunk_size = calc_chunk_size(size);
auto info = chunk_storage_info(inf, chunk_size);
auto info = chunk_storage_info(chunk_size);
if (info == nullptr) return nullptr;
return info->at(chunk_size, id)->data();
}
void release_storage(ipc::storage_id_t id, conn_info_head *inf, std::size_t size) {
void release_storage(ipc::storage_id_t id, std::size_t size) {
if (id < 0) {
ipc::error("[release_storage] id is invalid: id = %ld, size = %zd\n", (long)id, size);
return;
}
std::size_t chunk_size = calc_chunk_size(size);
auto info = chunk_storage_info(inf, chunk_size);
auto info = chunk_storage_info(chunk_size);
if (info == nullptr) return;
info->lock_.lock();
info->pool_.release(id);
@ -328,13 +231,13 @@ bool sub_rc(ipc::wr<Rp, Rc, ipc::trans::broadcast>,
}
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) {
void recycle_storage(ipc::storage_id_t id, std::size_t size, ipc::circ::cc_t curr_conns, ipc::circ::cc_t conn_id) {
if (id < 0) {
ipc::error("[recycle_storage] id is invalid: id = %ld, size = %zd\n", (long)id, size);
return;
}
std::size_t chunk_size = calc_chunk_size(size);
auto info = chunk_storage_info(inf, chunk_size);
auto info = chunk_storage_info(chunk_size);
if (info == nullptr) return;
auto chunk = info->at(chunk_size, id);
@ -349,7 +252,7 @@ void recycle_storage(ipc::storage_id_t id, conn_info_head *inf, std::size_t size
}
template <typename MsgT>
bool clear_message(conn_info_head *inf, void* p) {
bool clear_message(void* p) {
auto msg = static_cast<MsgT*>(p);
if (msg->storage_) {
std::int32_t r_size = static_cast<std::int32_t>(ipc::data_length) + msg->remain_;
@ -357,23 +260,58 @@ bool clear_message(conn_info_head *inf, void* p) {
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));
release_storage(
*reinterpret_cast<ipc::storage_id_t*>(&msg->data_),
static_cast<std::size_t>(r_size));
}
return true;
}
struct conn_info_head {
ipc::string name_;
msg_id_t cc_id_; // connection-info id
ipc::waiter cc_waiter_, wt_waiter_, rd_waiter_;
ipc::shm::handle acc_h_;
conn_info_head(char const * name)
: name_ {name}
, cc_id_ {(cc_acc() == nullptr) ? 0 : cc_acc()->fetch_add(1, std::memory_order_relaxed)}
, cc_waiter_{("__CC_CONN__" + name_).c_str()}
, wt_waiter_{("__WT_CONN__" + name_).c_str()}
, rd_waiter_{("__RD_CONN__" + name_).c_str()}
, acc_h_ {("__AC_CONN__" + name_).c_str(), sizeof(acc_t)} {
}
void quit_waiting() {
cc_waiter_.quit_waiting();
wt_waiter_.quit_waiting();
rd_waiter_.quit_waiting();
}
auto acc() {
return static_cast<acc_t*>(acc_h_.get());
}
auto& recv_cache() {
thread_local ipc::unordered_map<msg_id_t, cache_t> tls;
return tls;
}
};
template <typename W, typename F>
bool wait_for(W& waiter, F&& pred, std::uint64_t tm) {
bool wait_for(W& waiter, F&& pred, std::size_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);
bool loop = true, ret = true;
ipc::sleep(k, [&k, &loop, &ret, &waiter, &pred, tm] {
ret = waiter.wait_if([&loop, &pred] {
return loop = pred();
}, tm);
k = 0;
});
if (!ret) return false; // timeout or fail
if (k == 0) break; // k has been reset
if (!ret ) return false; // timeout or fail
if (!loop) break;
}
return true;
}
@ -388,32 +326,11 @@ struct queue_generator {
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__",
this->name_,
"__", ipc::to_string(DataSize),
"__", ipc::to_string(AlignSize)}).c_str());
}
}
void clear() noexcept {
que_.clear();
conn_info_head::clear();
}
static void clear_storage(char const * prefix, char const * name) noexcept {
queue_t::clear_storage(ipc::make_prefix(ipc::make_string(prefix), {
"QU_CONN__",
ipc::make_string(name),
"__", ipc::to_string(DataSize),
"__", ipc::to_string(AlignSize)}).c_str());
conn_info_head::clear_storage(prefix, name);
conn_info_t(char const * name)
: conn_info_head{name}
, que_{("__QU_CONN__" +
ipc::to_string(DataSize) + "__" +
ipc::to_string(AlignSize) + "__" + name).c_str()} {
}
void disconnect_receiver() {
@ -444,18 +361,6 @@ constexpr static queue_t* queue_of(ipc::handle_t h) noexcept {
/* 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) {
@ -473,7 +378,6 @@ static bool reconnect(ipc::handle_t * ph, bool start_to_recv) {
if (que == nullptr) {
return false;
}
info_of(*ph)->init();
if (start_to_recv) {
que->shut_sending();
if (que->connect()) { // wouldn't connect twice
@ -489,7 +393,16 @@ static bool reconnect(ipc::handle_t * ph, bool start_to_recv) {
return que->ready_sending();
}
static void destroy(ipc::handle_t h) noexcept {
static bool connect(ipc::handle_t * ph, char const * name, bool start_to_recv) {
assert(ph != nullptr);
if (*ph == nullptr) {
*ph = ipc::mem::alloc<conn_info_t>(name);
}
return reconnect(ph, start_to_recv);
}
static void destroy(ipc::handle_t h) {
disconnect(h);
ipc::mem::free(info_of(h));
}
@ -501,7 +414,7 @@ static std::size_t recv_count(ipc::handle_t h) noexcept {
return que->conn_count();
}
static bool wait_for_recv(ipc::handle_t h, std::size_t r_count, std::uint64_t tm) {
static bool wait_for_recv(ipc::handle_t h, std::size_t r_count, std::size_t tm) {
auto que = queue_of(h);
if (que == nullptr) {
return false;
@ -536,16 +449,15 @@ static bool send(F&& gen_push, ipc::handle_t h, void const * data, std::size_t s
return false;
}
// calc a new message id
conn_info_t *inf = info_of(h);
auto acc = inf->acc();
auto acc = info_of(h)->acc();
if (acc == nullptr) {
ipc::error("fail: send, info_of(h)->acc() == nullptr\n");
return false;
}
auto msg_id = acc->fetch_add(1, std::memory_order_relaxed);
auto try_push = std::forward<F>(gen_push)(inf, que, msg_id);
auto try_push = std::forward<F>(gen_push)(info_of(h), que, msg_id);
if (size > ipc::large_msg_limit) {
auto dat = acquire_storage(inf, size, conns);
auto dat = acquire_storage(size, conns);
void * buf = dat.second;
if (buf != nullptr) {
std::memcpy(buf, data, size);
@ -575,8 +487,8 @@ static bool send(F&& gen_push, ipc::handle_t h, void const * data, std::size_t s
return true;
}
static bool send(ipc::handle_t h, void const * data, std::size_t size, std::uint64_t tm) {
return send([tm](auto *info, auto *que, auto msg_id) {
static bool send(ipc::handle_t h, void const * data, std::size_t size, std::size_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(
@ -585,7 +497,7 @@ static bool send(ipc::handle_t h, void const * data, std::size_t size, std::uint
}, 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); },
clear_message<typename queue_t::value_t>,
info->cc_id_, msg_id, remain, data, size)) {
return false;
}
@ -596,8 +508,8 @@ static bool send(ipc::handle_t h, void const * data, std::size_t size, std::uint
}, 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) {
static bool try_send(ipc::handle_t h, void const * data, std::size_t size, std::size_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(
@ -612,7 +524,7 @@ static bool try_send(ipc::handle_t h, void const * data, std::size_t size, std::
}, h, data, size);
}
static ipc::buff_t recv(ipc::handle_t h, std::uint64_t tm) {
static ipc::buff_t recv(ipc::handle_t h, std::size_t tm) {
auto que = queue_of(h);
if (que == nullptr) {
ipc::error("fail: recv, queue_of(h) == nullptr\n");
@ -622,22 +534,18 @@ static ipc::buff_t recv(ipc::handle_t h, std::uint64_t tm) {
// hasn't connected yet, just return.
return {};
}
conn_info_t *inf = info_of(h);
auto& rc = inf->recv_cache();
auto& rc = info_of(h)->recv_cache();
for (;;) {
// pop a new message
typename queue_t::value_t msg {};
if (!wait_for(inf->rd_waiter_, [que, &msg, &h] {
if (!que->connected()) {
reconnect(&h, true);
}
typename queue_t::value_t msg;
if (!wait_for(info_of(h)->rd_waiter_, [que, &msg] {
return !que->pop(msg);
}, tm)) {
// pop failed, just return.
return {};
}
inf->wt_waiter_.broadcast();
if ((inf->acc() != nullptr) && (msg.cc_id_ == inf->cc_id_)) {
info_of(h)->wt_waiter_.broadcast();
if ((info_of(h)->acc() != nullptr) && (msg.cc_id_ == info_of(h)->cc_id_)) {
continue; // ignore message to self
}
// msg.remain_ may minus & abs(msg.remain_) < data_length
@ -650,18 +558,14 @@ static ipc::buff_t recv(ipc::handle_t h, std::uint64_t tm) {
// 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);
void* buf = find_storage(buf_id, 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()
buf_id, que->elems()->connections(std::memory_order_relaxed), que->connected_id()
});
if (r_info == nullptr) {
ipc::log("fail: ipc::mem::alloc<recycle_t>.\n");
@ -672,11 +576,7 @@ static ipc::buff_t recv(ipc::handle_t h, std::uint64_t tm) {
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);
recycle_storage<flag_t>(r_info->storage_id, size, r_info->curr_conns, r_info->conn_id);
}, r_info};
}
} else {
@ -734,22 +634,11 @@ using policy_t = ipc::policy::choose<ipc::circ::elem_array, Flag>;
namespace ipc {
template <typename Flag>
ipc::handle_t chan_impl<Flag>::init_first() {
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);
@ -762,64 +651,37 @@ void chan_impl<Flag>::disconnect(ipc::handle_t h) {
template <typename Flag>
void chan_impl<Flag>::destroy(ipc::handle_t h) {
disconnect(h);
detail_impl<policy_t<Flag>>::destroy(h);
}
template <typename Flag>
void chan_impl<Flag>::release(ipc::handle_t h) noexcept {
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);
auto info = detail_impl<policy_t<Flag>>::info_of(h);
return (info == nullptr) ? nullptr : info->name_.c_str();
}
template <typename Flag>
void chan_impl<Flag>::clear(ipc::handle_t h) noexcept {
disconnect(h);
using conn_info_t = typename detail_impl<policy_t<Flag>>::conn_info_t;
auto conn_info_p = static_cast<conn_info_t *>(h);
if (conn_info_p == nullptr) return;
conn_info_p->clear();
destroy(h);
}
template <typename Flag>
void chan_impl<Flag>::clear_storage(char const * name) noexcept {
chan_impl<Flag>::clear_storage({nullptr}, name);
}
template <typename Flag>
void chan_impl<Flag>::clear_storage(prefix pref, char const * name) noexcept {
using conn_info_t = typename detail_impl<policy_t<Flag>>::conn_info_t;
conn_info_t::clear_storage(pref.str, name);
}
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) {
bool chan_impl<Flag>::wait_for_recv(ipc::handle_t h, std::size_t r_count, std::size_t tm) {
return detail_impl<policy_t<Flag>>::wait_for_recv(h, r_count, tm);
}
template <typename Flag>
bool chan_impl<Flag>::send(ipc::handle_t h, void const * data, std::size_t size, std::uint64_t tm) {
bool chan_impl<Flag>::send(ipc::handle_t h, void const * data, std::size_t size, std::size_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) {
buff_t chan_impl<Flag>::recv(ipc::handle_t h, std::size_t tm) {
return detail_impl<policy_t<Flag>>::recv(h, tm);
}
template <typename Flag>
bool chan_impl<Flag>::try_send(ipc::handle_t h, void const * data, std::size_t size, std::uint64_t tm) {
bool chan_impl<Flag>::try_send(ipc::handle_t h, void const * data, std::size_t size, std::size_t tm) {
return detail_impl<policy_t<Flag>>::try_send(h, data, size, tm);
}
@ -829,8 +691,8 @@ buff_t chan_impl<Flag>::try_recv(ipc::handle_t 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::unicast >>;
template struct chan_impl<ipc::wr<relat::multi , relat::multi , trans::unicast >>;
template struct chan_impl<ipc::wr<relat::single, relat::multi , trans::broadcast>>;
template struct chan_impl<ipc::wr<relat::multi , relat::multi , trans::broadcast>>;

View File

@ -37,8 +37,8 @@ private:
elem_t block_[elem_max] {};
/**
* \remarks 'warning C4348: redefinition of default parameter' with MSVC.
* \see
* @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
*/

View File

@ -60,7 +60,7 @@ public:
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 == curr) {
if (next == 0) {
// connection-slot is full.
return 0;
}
@ -74,10 +74,6 @@ public:
return this->cc_.fetch_and(~cc_id, std::memory_order_acq_rel) & ~cc_id;
}
bool connected(cc_t cc_id) const noexcept {
return (this->connections() & cc_id) != 0;
}
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
@ -104,11 +100,6 @@ public:
}
}
bool connected(cc_t cc_id) const noexcept {
// In non-broadcast mode, connection tags are only used for counting.
return (this->connections() != 0) && (cc_id != 0);
}
std::size_t conn_count(std::memory_order order = std::memory_order_acquire) const noexcept {
return this->connections(order);
}

View File

@ -19,17 +19,17 @@ namespace mem {
class static_alloc {
public:
static void swap(static_alloc&) noexcept {}
static void swap(static_alloc&) {}
static void* alloc(std::size_t size) noexcept {
static void* alloc(std::size_t size) {
return size ? std::malloc(size) : nullptr;
}
static void free(void* p) noexcept {
static void free(void* p) {
std::free(p);
}
static void free(void* p, std::size_t /*size*/) noexcept {
static void free(void* p, std::size_t /*size*/) {
free(p);
}
};

View File

@ -45,17 +45,14 @@ 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>>
Key, T, std::hash<Key>, std::equal_to<Key>, ipc::mem::allocator<std::pair<const Key, T>>
>;
template <typename Key, typename T>
using map = std::map<
Key, T, std::less<Key>, ipc::mem::allocator<std::pair<Key const, T>>
Key, T, std::less<Key>, ipc::mem::allocator<std::pair<const Key, T>>
>;
template <typename Char>
@ -66,18 +63,6 @@ using basic_string = std::basic_string<
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] {};
@ -87,24 +72,4 @@ ipc::string to_string(T val) {
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

@ -1,24 +1,4 @@
#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(__FreeBSD__)
# define IPC_OS_FREEBSD_
#elif defined(__QNX__)
# define IPC_OS_QNX_
#elif defined(__APPLE__)
#elif defined(__ANDROID__)
// TBD
#endif
#if defined(__cplusplus)
#pragma once
#include <memory>
#include <mutex>
@ -72,9 +52,15 @@
#if __cplusplus >= 201703L
namespace std {
// C++17 and later: std library already provides deduction guides
// No need to add custom ones, just use the standard ones directly
// 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 {
@ -125,8 +111,17 @@ constexpr const T& (min)(const T& a, const T& b) {
#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;
}
} // namespace detail
} // namespace ipc
#endif // defined(__cplusplus)
#endif // LIBIPC_SRC_PLATFORM_DETAIL_H_

View File

@ -1,24 +0,0 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org>

View File

@ -1,213 +0,0 @@
<h1 align="center">
<br>
<img src="https://raw.githubusercontent.com/alephzero/logo/master/rendered/alephzero.svg" width="256px">
<br>
AlephZero
</h1>
<h3 align="center">Simple, Robust, Fast IPC.</h3>
<p align="center">
<a href="https://github.com/alephzero/alephzero/actions?query=workflow%3ACI"><img src="https://github.com/alephzero/alephzero/workflows/CI/badge.svg"></a>
<a href="https://codecov.io/gh/alephzero/alephzero"><img src="https://codecov.io/gh/alephzero/alephzero/branch/master/graph/badge.svg"></a>
<a href="https://alephzero.readthedocs.io/en/latest/?badge=latest"><img src="https://readthedocs.org/projects/alephzero/badge/?version=latest"></a>
<a href="http://unlicense.org"><img src="https://img.shields.io/badge/license-Unlicense-blue.svg"></a>
</p>
<p align="center">
<a href="#overview">Overview</a>
<a href="#transport">Transport</a>
<a href="#protocol">Protocol</a>
<a href="#examples">Examples</a>
<a href="#installation">Installation</a>
<a href="#across-dockers">Across Dockers</a>
</p>
# Overview
[Presentation from March 25, 2020](https://docs.google.com/presentation/d/12KE9UucjZPtpVnM1NljxOqBolBBKECWJdrCoE2yJaBw/edit#slide=id.p)
AlephZero is a library for message based communication between programs running on the same machine.
## Simple
AlephZero's main goal is to be simple to use. Nothing is higher priority.
There is no "master" process in between your nodes that is needed to do handshakes or exchanges of any kind. All you need is the topic name.
See the <a href="#examples">Examples</a>.
## Robust
This is probably the main value of AlephZero, above similar libraries.
AlephZero uses a lot of tricks to ensure the state of all channels is consistent, even when programs die. This includes double-buffering the state of the communication channel and [robustifying](https://man7.org/linux/man-pages/man3/pthread_mutexattr_setrobust.3.html) the locks and notification channels.
## Fast
AlephZero uses shared memory across multiple processes to read and write messages, minimizing the involvement of the kernel. The kernel only really gets involved in notifying a process that a new message exists, and for that we use futex (fast user-space mutex).
TODO: Benchmarks
# Transport
AlephZero, at its core, is a simple allocator on top of a contiguous region of memory. Usually, shared-memory. The allocator of choice is a circular-linked-list, which is fast, simple, and sufficient for the protocol listed below. It also plays well with the robustness requirement.
This has a number of implications. For one, this means that old messages are kept around until the space is needed. The oldest messages are always discarded before any more recent messages.
# Protocol
Rather than exposing the low-level transport directly, AlephZero provides a few higher level protocol:
* <b>PubSub</b>: Broadcast published messages. Subscribers get notified.
* <b>RPC</b>: Request-response.
* <b>PRPC (Progressive RPC)</b>: Request-streaming response.
* <b>Sessions</b>: Bi-directional channel of communication. Not yet implemented. Let me know if you want this.
# Examples
Many more example and an interactive experience can be found at: https://github.com/alephzero/playground
For the curious, here are some simple snippets to get you started:
To begin with, we need to include AlephZero:
```cc
#include <a0.h>
```
## PubSub
You can have as many publisher and subscribers on the same topic as you wish. They just need to agree on the filename.
```cc
a0::Publisher p("my_pubsub_topic");
p.pub("foo");
```
You just published `"foo"` to the `"my_pubsub_topic"`.
To read those message, you can create a subscriber on the same topic:
```cc
a0::Subscriber sub(
"my_pubsub_topic",
A0_INIT_AWAIT_NEW, // or MOST_RECENT or OLDEST
A0_ITER_NEWEST, // or NEXT
[](a0::PacketView pkt_view) {
std::cout << "Got: " << pkt_view.payload() << std::endl;
});
```
The callback will trigger whenever a message is published.
The `Subscriber` object spawns a thread that will read the topic and call the callback.
The `A0_INIT` tells the subscriber where to start reading.
* `A0_INIT_AWAIT_NEW`: Start with messages published after the creation of the subscriber.
* `A0_INIT_MOST_RECENT`: Start with the most recently published message. Useful for state and configuration. But be careful, this can be quite old!
* `A0_INIT_OLDEST`: Topics keep a history of 16MB (unless configures otherwise). Start with the oldest thing still in there.
The `A0_ITER` tells the subscriber how to continue reading messages. After each callback:
* `A0_ITER_NEXT`: grab the sequentially next message. When you don't want to miss a thing.
* `A0_ITER_NEWEST`: grab the newest available unread message. When you want to keep up with the firehose.
```cc
a0::SubscriberSync sub_sync(
"my_pubsub_topic",
A0_INIT_OLDEST, A0_ITER_NEXT);
while (sub_sync.has_next()) {
auto pkt = sub_sync.next();
std::cout << "Got: " << pkt.payload() << std::endl;
}
```
## RPC
Create an `RpcServer`:
```cc
a0::RpcServer server(
"my_rpc_topic",
/* onrequest = */ [](a0::RpcRequest req) {
std::cout << "Got: " << req.pkt().payload() << std::endl;
req.reply("echo " + std::string(req.pkt().payload()));
},
/* oncancel = */ nullptr);
```
Create an `RpcClient`:
```cc
a0::RpcClient client("my_rpc_topic");
client.send("client msg", [](a0::PacketView reply) {
std::cout << "Got: " << reply.payload() << std::endl;
});
```
# Installation
## Install From Source
### Ubuntu Dependencies
```sh
apt install g++ make
```
### Alpine Dependencies
```sh
apk add g++ linux-headers make
```
### Download And Install
```sh
git clone https://github.com/alephzero/alephzero.git
cd alephzero
make install -j
```
## Install From Package
Coming soon-ish. Let me know if you want this and I'll prioritize it. External support is much appreciated.
## Integration
### Command Line
Add the following to g++ / clang commands.
```sh
-L${libdir} -lalephzero -lpthread
```
### Package-cfg
```sh
pkg-config --cflags --libs alephzero
```
### CMake
Coming soon-ish. Let me know if you want this and I'll prioritize it. External support is much appreciated.
### Bazel
Coming soon-ish. Let me know if you want this and I'll prioritize it.
# Across Dockers
For programs running across different dockers to be able to communicate, we need to have them match up on two flags: `--ipc` and `--pid`.
* `--ipc` shares the `/dev/shm` filesystem. This is necessary to open the same file topics.
* `--pid` shares the process id namespace. This is necessary for the locking and notification systems.
In the simplest case, you can set them both to `host` and talk through the system's global `/dev/shm` and process id namespace.
```sh
docker run --ipc=host --pid=host --name=foo foo_image
docker run --ipc=host --pid=host --name=bar bar_image
```
Or, you can mark one as `shareable` and have the others connect to it:
```sh
docker run --ipc=shareable --pid=shareable --name=foo foo_image
docker run --ipc=container:foo --pid=container:foo --name=bar bar_image
```

View File

@ -1,36 +0,0 @@
#ifndef A0_SRC_ATOMIC_H
#define A0_SRC_ATOMIC_H
#include "a0/inline.h"
#ifdef __cplusplus
extern "C" {
#endif
A0_STATIC_INLINE
void a0_barrier() {
// 'atomic_thread_fence' is not supported with -fsanitize=thread
__sync_synchronize();
}
#define a0_atomic_fetch_add(P, V) __atomic_fetch_add((P), (V), __ATOMIC_RELAXED)
#define a0_atomic_add_fetch(P, V) __atomic_add_fetch((P), (V), __ATOMIC_RELAXED)
#define a0_atomic_fetch_and(P, V) __atomic_fetch_and((P), (V), __ATOMIC_RELAXED)
#define a0_atomic_and_fetch(P, V) __atomic_and_fetch((P), (V), __ATOMIC_RELAXED)
#define a0_atomic_fetch_or(P, V) __atomic_fetch_or((P), (V), __ATOMIC_RELAXED)
#define a0_atomic_or_fetch(P, V) __atomic_or_fetch((P), (V), __ATOMIC_RELAXED)
#define a0_atomic_load(P) __atomic_load_n((P), __ATOMIC_RELAXED)
#define a0_atomic_store(P, V) __atomic_store_n((P), (V), __ATOMIC_RELAXED)
// TODO(lshamis): Switch from __sync to __atomic.
#define a0_cas_val(P, OV, NV) __sync_val_compare_and_swap((P), (OV), (NV))
#define a0_cas(P, OV, NV) __sync_bool_compare_and_swap((P), (OV), (NV))
#ifdef __cplusplus
}
#endif
#endif // A0_SRC_ATOMIC_H

View File

@ -1,60 +0,0 @@
#ifndef A0_SRC_CLOCK_H
#define A0_SRC_CLOCK_H
#include "a0/err.h"
#include "a0/inline.h"
#include <stdint.h>
#include <time.h>
#include "err_macro.h"
#ifdef __cplusplus
extern "C" {
#endif
static const int64_t NS_PER_SEC = 1e9;
typedef struct timespec timespec_t;
A0_STATIC_INLINE
a0_err_t a0_clock_now(clockid_t clk, timespec_t* out) {
A0_RETURN_SYSERR_ON_MINUS_ONE(clock_gettime(clk, out));
return A0_OK;
}
A0_STATIC_INLINE
a0_err_t a0_clock_add(timespec_t ts, int64_t add_nsec, timespec_t* out) {
out->tv_sec = ts.tv_sec + add_nsec / NS_PER_SEC;
out->tv_nsec = ts.tv_nsec + add_nsec % NS_PER_SEC;
if (out->tv_nsec >= NS_PER_SEC) {
out->tv_sec++;
out->tv_nsec -= NS_PER_SEC;
} else if (out->tv_nsec < 0) {
out->tv_sec--;
out->tv_nsec += NS_PER_SEC;
}
return A0_OK;
}
A0_STATIC_INLINE
a0_err_t a0_clock_convert(
clockid_t orig_clk,
timespec_t orig_ts,
clockid_t target_clk,
timespec_t* target_ts) {
timespec_t orig_now;
A0_RETURN_ERR_ON_ERR(a0_clock_now(orig_clk, &orig_now));
timespec_t target_now;
A0_RETURN_ERR_ON_ERR(a0_clock_now(target_clk, &target_now));
int64_t add_nsec = (orig_ts.tv_sec - orig_now.tv_sec) * NS_PER_SEC + (orig_ts.tv_nsec - orig_now.tv_nsec);
return a0_clock_add(target_now, add_nsec, target_ts);
}
#ifdef __cplusplus
}
#endif
#endif // A0_SRC_CLOCK_H

View File

@ -1,13 +0,0 @@
#ifndef A0_EMPTY_H
#define A0_EMPTY_H
// Bah. Why is there no consistent way to zero initialize a struct?
#ifdef __cplusplus
#define A0_EMPTY \
{}
#else
#define A0_EMPTY \
{ 0 }
#endif
#endif // A0_EMPTY_H

View File

@ -1,50 +0,0 @@
#include "a0/err.h"
#include "a0/thread_local.h"
#include <errno.h>
#include <string.h>
A0_THREAD_LOCAL int a0_err_syscode;
A0_THREAD_LOCAL char a0_err_msg[1024];
const char* a0_strerror(a0_err_t err) {
switch (err) {
case A0_OK: {
return strerror(0);
}
case A0_ERR_SYS: {
return strerror(a0_err_syscode);
}
case A0_ERR_CUSTOM_MSG: {
return a0_err_msg;
}
case A0_ERR_INVALID_ARG: {
return strerror(EINVAL);
}
case A0_ERR_RANGE: {
return "Index out of bounds";
}
case A0_ERR_AGAIN: {
return "Not available yet";
}
case A0_ERR_FRAME_LARGE: {
return "Frame size too large";
}
case A0_ERR_ITER_DONE: {
return "Done iterating";
}
case A0_ERR_NOT_FOUND: {
return "Not found";
}
case A0_ERR_BAD_PATH: {
return "Invalid path";
}
case A0_ERR_BAD_TOPIC: {
return "Invalid topic name";
}
default: {
break;
}
}
return "";
}

View File

@ -1,33 +0,0 @@
#ifndef A0_ERR_H
#define A0_ERR_H
#include "a0/thread_local.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum a0_err_e {
A0_OK = 0,
A0_ERR_SYS = 1,
A0_ERR_CUSTOM_MSG = 2,
A0_ERR_INVALID_ARG = 3,
A0_ERR_RANGE = 4,
A0_ERR_AGAIN = 5,
A0_ERR_ITER_DONE = 6,
A0_ERR_NOT_FOUND = 7,
A0_ERR_FRAME_LARGE = 8,
A0_ERR_BAD_PATH = 9,
A0_ERR_BAD_TOPIC = 10,
} a0_err_t;
extern A0_THREAD_LOCAL int a0_err_syscode;
extern A0_THREAD_LOCAL char a0_err_msg[1024];
const char* a0_strerror(a0_err_t);
#ifdef __cplusplus
}
#endif
#endif // A0_ERR_H

View File

@ -1,52 +0,0 @@
#ifndef A0_SRC_ERR_MACRO_H
#define A0_SRC_ERR_MACRO_H
#include "a0/err.h"
#include "a0/inline.h"
#include <errno.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
A0_STATIC_INLINE
a0_err_t A0_MAKE_SYSERR(int syserr) {
a0_err_syscode = syserr;
return A0_ERR_SYS;
}
A0_STATIC_INLINE
int A0_SYSERR(a0_err_t err) {
return err == A0_ERR_SYS ? a0_err_syscode : 0;
}
A0_STATIC_INLINE_RECURSIVE
a0_err_t A0_MAKE_MSGERR(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
if (fmt) {
vsnprintf(a0_err_msg, sizeof(a0_err_msg), fmt, args); // NOLINT(clang-analyzer-valist.Uninitialized): https://bugs.llvm.org/show_bug.cgi?id=41311
va_end(args);
return A0_ERR_CUSTOM_MSG;
}
va_end(args);
return A0_OK;
}
#define A0_RETURN_SYSERR_ON_MINUS_ONE(X) \
do { \
if ((X) == -1) { \
a0_err_syscode = errno; \
return A0_ERR_SYS; \
} \
} while (0)
#define A0_RETURN_ERR_ON_ERR(X) \
do { \
a0_err_t _err = (X); \
if (_err) { \
return _err; \
} \
} while (0)
#endif // A0_SRC_ERR_MACRO_H

View File

@ -1,111 +0,0 @@
#ifndef A0_SRC_FTX_H
#define A0_SRC_FTX_H
#include "a0/err.h"
#include "a0/inline.h"
#include "a0/time.h"
#include <limits.h>
#include <linux/futex.h>
#include <stdint.h>
#include <syscall.h>
#include <time.h>
#include <unistd.h>
#include "clock.h"
#include "err_macro.h"
#ifdef __cplusplus
extern "C" {
#endif
// FUTEX_WAIT and FUTEX_WAIT_REQUEUE_PI default to CLOCK_MONOTONIC,
// but FUTEX_LOCK_PI always uses CLOCK_REALTIME.
//
// Until someone tells me otherwise, I assume this is bad decision making
// and I will instead standardize all things on CLOCK_BOOTTIME.
// Futex.
// Operations rely on the address.
// It should not be copied or moved.
typedef uint32_t a0_ftx_t;
A0_STATIC_INLINE
a0_err_t a0_futex(a0_ftx_t* uaddr,
int futex_op,
int val,
uintptr_t timeout_or_val2,
a0_ftx_t* uaddr2,
int val3) {
A0_RETURN_SYSERR_ON_MINUS_ONE(syscall(SYS_futex, uaddr, futex_op, val, timeout_or_val2, uaddr2, val3));
return A0_OK;
}
A0_STATIC_INLINE
a0_err_t a0_ftx_wait(a0_ftx_t* ftx, int confirm_val, const a0_time_mono_t* time_mono) {
if (!time_mono) {
return a0_futex(ftx, FUTEX_WAIT, confirm_val, 0, NULL, 0);
}
timespec_t ts_mono;
A0_RETURN_ERR_ON_ERR(a0_clock_convert(CLOCK_BOOTTIME, time_mono->ts, CLOCK_MONOTONIC, &ts_mono));
return a0_futex(ftx, FUTEX_WAIT, confirm_val, (uintptr_t)&ts_mono, NULL, 0);
}
A0_STATIC_INLINE
a0_err_t a0_ftx_wake(a0_ftx_t* ftx, int cnt) {
return a0_futex(ftx, FUTEX_WAKE, cnt, 0, NULL, 0);
}
A0_STATIC_INLINE
a0_err_t a0_ftx_signal(a0_ftx_t* ftx) {
return a0_ftx_wake(ftx, 1);
}
A0_STATIC_INLINE
a0_err_t a0_ftx_broadcast(a0_ftx_t* ftx) {
return a0_ftx_wake(ftx, INT_MAX);
}
A0_STATIC_INLINE
a0_err_t a0_ftx_lock_pi(a0_ftx_t* ftx, const a0_time_mono_t* time_mono) {
if (!time_mono) {
return a0_futex(ftx, FUTEX_LOCK_PI, 0, 0, NULL, 0);
}
timespec_t ts_wall;
A0_RETURN_ERR_ON_ERR(a0_clock_convert(CLOCK_BOOTTIME, time_mono->ts, CLOCK_REALTIME, &ts_wall));
return a0_futex(ftx, FUTEX_LOCK_PI, 0, (uintptr_t)&ts_wall, NULL, 0);
}
A0_STATIC_INLINE
a0_err_t a0_ftx_trylock_pi(a0_ftx_t* ftx) {
return a0_futex(ftx, FUTEX_TRYLOCK_PI, 0, 0, NULL, 0);
}
A0_STATIC_INLINE
a0_err_t a0_ftx_unlock_pi(a0_ftx_t* ftx) {
return a0_futex(ftx, FUTEX_UNLOCK_PI, 0, 0, NULL, 0);
}
A0_STATIC_INLINE
a0_err_t a0_ftx_cmp_requeue_pi(a0_ftx_t* ftx, int confirm_val, a0_ftx_t* requeue_ftx, int max_requeue) {
return a0_futex(ftx, FUTEX_CMP_REQUEUE_PI, 1, max_requeue, requeue_ftx, confirm_val);
}
A0_STATIC_INLINE
a0_err_t a0_ftx_wait_requeue_pi(a0_ftx_t* ftx, int confirm_val, const a0_time_mono_t* time_mono, a0_ftx_t* requeue_ftx) {
if (!time_mono) {
return a0_futex(ftx, FUTEX_WAIT_REQUEUE_PI, confirm_val, 0, requeue_ftx, 0);
}
timespec_t ts_mono;
A0_RETURN_ERR_ON_ERR(a0_clock_convert(CLOCK_BOOTTIME, time_mono->ts, CLOCK_MONOTONIC, &ts_mono));
return a0_futex(ftx, FUTEX_WAIT_REQUEUE_PI, confirm_val, (uintptr_t)&ts_mono, requeue_ftx, 0);
}
#ifdef __cplusplus
}
#endif
#endif // A0_SRC_FTX_H

View File

@ -1,8 +0,0 @@
#ifndef A0_INLINE_H
#define A0_INLINE_H
#define A0_STATIC_INLINE static inline __attribute__((always_inline))
#define A0_STATIC_INLINE_RECURSIVE static inline
#endif // A0_INLINE_H

View File

@ -1,420 +0,0 @@
#include "a0/err.h"
#include "a0/inline.h"
#include "a0/mtx.h"
#include "a0/thread_local.h"
#include "a0/tid.h"
#include "a0/time.h"
#include <errno.h>
#include <limits.h>
#include <linux/futex.h>
#include <pthread.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <syscall.h>
#include <time.h>
#include <unistd.h>
#include "atomic.h"
#include "clock.h"
#include "err_macro.h"
#include "ftx.h"
// TSAN is worth the pain of properly annotating our mutex.
// clang-format off
#if defined(__SANITIZE_THREAD__)
#define A0_TSAN_ENABLED
#elif defined(__has_feature)
#if __has_feature(thread_sanitizer)
#define A0_TSAN_ENABLED
#endif
#endif
// clang-format on
const unsigned __tsan_mutex_linker_init = 1 << 0;
const unsigned __tsan_mutex_write_reentrant = 1 << 1;
const unsigned __tsan_mutex_read_reentrant = 1 << 2;
const unsigned __tsan_mutex_not_static = 1 << 8;
const unsigned __tsan_mutex_read_lock = 1 << 3;
const unsigned __tsan_mutex_try_lock = 1 << 4;
const unsigned __tsan_mutex_try_lock_failed = 1 << 5;
const unsigned __tsan_mutex_recursive_lock = 1 << 6;
const unsigned __tsan_mutex_recursive_unlock = 1 << 7;
#ifdef A0_TSAN_ENABLED
void __tsan_mutex_create(void* addr, unsigned flags);
void __tsan_mutex_destroy(void* addr, unsigned flags);
void __tsan_mutex_pre_lock(void* addr, unsigned flags);
void __tsan_mutex_post_lock(void* addr, unsigned flags, int recursion);
int __tsan_mutex_pre_unlock(void* addr, unsigned flags);
void __tsan_mutex_post_unlock(void* addr, unsigned flags);
void __tsan_mutex_pre_signal(void* addr, unsigned flags);
void __tsan_mutex_post_signal(void* addr, unsigned flags);
void __tsan_mutex_pre_divert(void* addr, unsigned flags);
void __tsan_mutex_post_divert(void* addr, unsigned flags);
#else
#define _u_ __attribute__((unused))
A0_STATIC_INLINE void _u_ __tsan_mutex_create(_u_ void* addr, _u_ unsigned flags) {}
A0_STATIC_INLINE void _u_ __tsan_mutex_destroy(_u_ void* addr, _u_ unsigned flags) {}
A0_STATIC_INLINE void _u_ __tsan_mutex_pre_lock(_u_ void* addr, _u_ unsigned flags) {}
A0_STATIC_INLINE void _u_ __tsan_mutex_post_lock(_u_ void* addr,
_u_ unsigned flags,
_u_ int recursion) {}
A0_STATIC_INLINE int _u_ __tsan_mutex_pre_unlock(_u_ void* addr, _u_ unsigned flags) {
return 0;
}
A0_STATIC_INLINE void _u_ __tsan_mutex_post_unlock(_u_ void* addr, _u_ unsigned flags) {}
A0_STATIC_INLINE void _u_ __tsan_mutex_pre_signal(_u_ void* addr, _u_ unsigned flags) {}
A0_STATIC_INLINE void _u_ __tsan_mutex_post_signal(_u_ void* addr, _u_ unsigned flags) {}
A0_STATIC_INLINE void _u_ __tsan_mutex_pre_divert(_u_ void* addr, _u_ unsigned flags) {}
A0_STATIC_INLINE void _u_ __tsan_mutex_post_divert(_u_ void* addr, _u_ unsigned flags) {}
#endif
A0_THREAD_LOCAL bool a0_robust_init = false;
A0_STATIC_INLINE
void a0_robust_reset() {
a0_robust_init = 0;
}
A0_STATIC_INLINE
void a0_robust_reset_atfork() {
pthread_atfork(NULL, NULL, &a0_robust_reset);
}
static pthread_once_t a0_robust_reset_atfork_once;
typedef struct robust_list robust_list_t;
typedef struct robust_list_head robust_list_head_t;
A0_THREAD_LOCAL robust_list_head_t a0_robust_head;
A0_STATIC_INLINE
void robust_init() {
a0_robust_head.list.next = &a0_robust_head.list;
a0_robust_head.futex_offset = offsetof(a0_mtx_t, ftx);
a0_robust_head.list_op_pending = NULL;
syscall(SYS_set_robust_list, &a0_robust_head.list, sizeof(a0_robust_head));
}
A0_STATIC_INLINE
void init_thread() {
if (a0_robust_init) {
return;
}
pthread_once(&a0_robust_reset_atfork_once, a0_robust_reset_atfork);
robust_init();
a0_robust_init = true;
}
A0_STATIC_INLINE
void robust_op_start(a0_mtx_t* mtx) {
init_thread();
a0_robust_head.list_op_pending = (struct robust_list*)mtx;
a0_barrier();
}
A0_STATIC_INLINE
void robust_op_end(a0_mtx_t* mtx) {
(void)mtx;
a0_barrier();
a0_robust_head.list_op_pending = NULL;
}
A0_STATIC_INLINE
bool robust_is_head(a0_mtx_t* mtx) {
return mtx == (a0_mtx_t*)&a0_robust_head;
}
A0_STATIC_INLINE
void robust_op_add(a0_mtx_t* mtx) {
a0_mtx_t* old_first = (a0_mtx_t*)a0_robust_head.list.next;
mtx->prev = (a0_mtx_t*)&a0_robust_head;
mtx->next = old_first;
a0_barrier();
a0_robust_head.list.next = (robust_list_t*)mtx;
if (!robust_is_head(old_first)) {
old_first->prev = mtx;
}
}
A0_STATIC_INLINE
void robust_op_del(a0_mtx_t* mtx) {
a0_mtx_t* prev = mtx->prev;
a0_mtx_t* next = mtx->next;
prev->next = next;
if (!robust_is_head(next)) {
next->prev = prev;
}
}
A0_STATIC_INLINE
uint32_t ftx_tid(a0_ftx_t ftx) {
return ftx & FUTEX_TID_MASK;
}
A0_STATIC_INLINE
bool ftx_owner_died(a0_ftx_t ftx) {
return ftx & FUTEX_OWNER_DIED;
}
static const uint32_t FTX_NOTRECOVERABLE = FUTEX_TID_MASK | FUTEX_OWNER_DIED;
A0_STATIC_INLINE
bool ftx_notrecoverable(a0_ftx_t ftx) {
return (ftx & FTX_NOTRECOVERABLE) == FTX_NOTRECOVERABLE;
}
A0_STATIC_INLINE
a0_err_t a0_mtx_timedlock_robust(a0_mtx_t* mtx, const a0_time_mono_t* timeout) {
const uint32_t tid = a0_tid();
int syserr = EINTR;
while (syserr == EINTR) {
// Can't lock if borked.
if (ftx_notrecoverable(a0_atomic_load(&mtx->ftx))) {
return A0_MAKE_SYSERR(ENOTRECOVERABLE);
}
// Try to lock without kernel involvement.
if (a0_cas(&mtx->ftx, 0, tid)) {
return A0_OK;
}
// Ask the kernel to lock.
syserr = A0_SYSERR(a0_ftx_lock_pi(&mtx->ftx, timeout));
}
if (!syserr) {
if (ftx_owner_died(a0_atomic_load(&mtx->ftx))) {
return A0_MAKE_SYSERR(EOWNERDEAD);
}
return A0_OK;
}
return A0_MAKE_SYSERR(syserr);
}
A0_STATIC_INLINE
a0_err_t a0_mtx_timedlock_impl(a0_mtx_t* mtx, const a0_time_mono_t* timeout) {
// Note: __tsan_mutex_pre_lock should come here, but tsan doesn't provide
// a way to "fail" a lock. Only a trylock.
robust_op_start(mtx);
const a0_err_t err = a0_mtx_timedlock_robust(mtx, timeout);
if (!err || A0_SYSERR(err) == EOWNERDEAD) {
__tsan_mutex_pre_lock(mtx, 0);
robust_op_add(mtx);
__tsan_mutex_post_lock(mtx, 0, 0);
}
robust_op_end(mtx);
return err;
}
a0_err_t a0_mtx_timedlock(a0_mtx_t* mtx, a0_time_mono_t timeout) {
return a0_mtx_timedlock_impl(mtx, &timeout);
}
a0_err_t a0_mtx_lock(a0_mtx_t* mtx) {
return a0_mtx_timedlock_impl(mtx, NULL);
}
A0_STATIC_INLINE
a0_err_t a0_mtx_trylock_impl(a0_mtx_t* mtx) {
const uint32_t tid = a0_tid();
// Try to lock without kernel involvement.
uint32_t old = a0_cas_val(&mtx->ftx, 0, tid);
// Did it work?
if (!old) {
robust_op_add(mtx);
return A0_OK;
}
// Is the lock still usable?
if (ftx_notrecoverable(old)) {
return A0_MAKE_SYSERR(ENOTRECOVERABLE);
}
// Is the owner still alive?
if (!ftx_owner_died(old)) {
return A0_MAKE_SYSERR(EBUSY);
}
// Oh, the owner died. Ask the kernel to fix the state.
a0_err_t err = a0_ftx_trylock_pi(&mtx->ftx);
if (!err) {
robust_op_add(mtx);
if (ftx_owner_died(a0_atomic_load(&mtx->ftx))) {
return A0_MAKE_SYSERR(EOWNERDEAD);
}
return A0_OK;
}
// EAGAIN means that somebody else beat us to it.
// Anything else means we're borked.
if (A0_SYSERR(err) == EAGAIN) {
return A0_MAKE_SYSERR(EBUSY);
}
return A0_MAKE_SYSERR(ENOTRECOVERABLE);
}
a0_err_t a0_mtx_trylock(a0_mtx_t* mtx) {
__tsan_mutex_pre_lock(mtx, __tsan_mutex_try_lock);
robust_op_start(mtx);
a0_err_t err = a0_mtx_trylock_impl(mtx);
robust_op_end(mtx);
if (!err || A0_SYSERR(err) == EOWNERDEAD) {
__tsan_mutex_post_lock(mtx, __tsan_mutex_try_lock, 0);
} else {
__tsan_mutex_post_lock(mtx, __tsan_mutex_try_lock | __tsan_mutex_try_lock_failed, 0);
}
return err;
}
a0_err_t a0_mtx_consistent(a0_mtx_t* mtx) {
const uint32_t val = a0_atomic_load(&mtx->ftx);
// Why fix what isn't broken?
if (!ftx_owner_died(val)) {
return A0_MAKE_SYSERR(EINVAL);
}
// Is it yours to fix?
if (ftx_tid(val) != a0_tid()) {
return A0_MAKE_SYSERR(EPERM);
}
// Fix it!
a0_atomic_and_fetch(&mtx->ftx, ~FUTEX_OWNER_DIED);
return A0_OK;
}
a0_err_t a0_mtx_unlock(a0_mtx_t* mtx) {
const uint32_t tid = a0_tid();
const uint32_t val = a0_atomic_load(&mtx->ftx);
// Only the owner can unlock.
if (ftx_tid(val) != tid) {
return A0_MAKE_SYSERR(EPERM);
}
__tsan_mutex_pre_unlock(mtx, 0);
// If the mutex was acquired with EOWNERDEAD, the caller is responsible
// for fixing the state and marking the mutex consistent. If they did
// not mark it consistent and are unlocking... then we are unrecoverably
// borked!
uint32_t new_val = 0;
if (ftx_owner_died(val)) {
new_val = FTX_NOTRECOVERABLE;
}
robust_op_start(mtx);
robust_op_del(mtx);
// If the futex is exactly equal to tid, then there are no waiters and the
// kernel doesn't need to get involved.
if (!a0_cas(&mtx->ftx, tid, new_val)) {
// Ask the kernel to wake up a waiter.
a0_ftx_unlock_pi(&mtx->ftx);
if (new_val) {
a0_atomic_or_fetch(&mtx->ftx, new_val);
}
}
robust_op_end(mtx);
__tsan_mutex_post_unlock(mtx, 0);
return A0_OK;
}
// TODO(lshamis): Handle ENOTRECOVERABLE
A0_STATIC_INLINE
a0_err_t a0_cnd_timedwait_impl(a0_cnd_t* cnd, a0_mtx_t* mtx, const a0_time_mono_t* timeout) {
const uint32_t init_cnd = a0_atomic_load(cnd);
// Unblock other threads to do the things that will eventually signal this wait.
a0_err_t err = a0_mtx_unlock(mtx);
if (err) {
return err;
}
__tsan_mutex_pre_lock(mtx, 0);
robust_op_start(mtx);
do {
// Priority-inheritance-aware wait until awoken or timeout.
err = a0_ftx_wait_requeue_pi(cnd, init_cnd, timeout, &mtx->ftx);
} while (A0_SYSERR(err) == EINTR);
// We need to manually lock on timeout.
// Note: We keep the timeout error.
if (A0_SYSERR(err) == ETIMEDOUT) {
a0_mtx_timedlock_robust(mtx, NULL);
}
// Someone else grabbed and mutated the resource between the unlock and wait.
// No need to wait.
if (A0_SYSERR(err) == EAGAIN) {
err = a0_mtx_timedlock_robust(mtx, NULL);
}
robust_op_add(mtx);
// If no higher priority error, check the previous owner didn't die.
if (!err) {
err = ftx_owner_died(a0_atomic_load(&mtx->ftx)) ? EOWNERDEAD : A0_OK;
}
robust_op_end(mtx);
__tsan_mutex_post_lock(mtx, 0, 0);
return err;
}
a0_err_t a0_cnd_timedwait(a0_cnd_t* cnd, a0_mtx_t* mtx, a0_time_mono_t timeout) {
// Let's not unlock the mutex if we're going to get EINVAL due to a bad timeout.
if ((timeout.ts.tv_sec < 0 || timeout.ts.tv_nsec < 0 || (!timeout.ts.tv_sec && !timeout.ts.tv_nsec) || timeout.ts.tv_nsec >= NS_PER_SEC)) {
return A0_MAKE_SYSERR(EINVAL);
}
return a0_cnd_timedwait_impl(cnd, mtx, &timeout);
}
a0_err_t a0_cnd_wait(a0_cnd_t* cnd, a0_mtx_t* mtx) {
return a0_cnd_timedwait_impl(cnd, mtx, NULL);
}
A0_STATIC_INLINE
a0_err_t a0_cnd_wake(a0_cnd_t* cnd, a0_mtx_t* mtx, uint32_t cnt) {
uint32_t val = a0_atomic_add_fetch(cnd, 1);
while (true) {
a0_err_t err = a0_ftx_cmp_requeue_pi(cnd, val, &mtx->ftx, cnt);
if (A0_SYSERR(err) != EAGAIN) {
return err;
}
// Another thread is also trying to wake this condition variable.
val = a0_atomic_load(cnd);
}
}
a0_err_t a0_cnd_signal(a0_cnd_t* cnd, a0_mtx_t* mtx) {
return a0_cnd_wake(cnd, mtx, 1);
}
a0_err_t a0_cnd_broadcast(a0_cnd_t* cnd, a0_mtx_t* mtx) {
return a0_cnd_wake(cnd, mtx, INT_MAX);
}

View File

@ -1,57 +0,0 @@
#ifndef A0_MTX_H
#define A0_MTX_H
#include "a0/err.h"
#include "a0/time.h"
#include "a0/unused.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef uint32_t a0_ftx_t;
// https://stackoverflow.com/questions/61645966/is-typedef-allowed-before-definition
struct a0_mtx_s;
typedef struct a0_mtx_s a0_mtx_t;
// Mutex implementation designed for IPC.
//
// Similar to pthread_mutex_t with the following flags fixed:
// * Process shared.
// * Robust.
// * Error checking.
// * Priority inheriting.
//
// Unlike pthread_mutex_t, timespec are expected to use CLOCK_BOOTTIME.
//
// struct a0_mtx_s "Inherits" from robust_list, which requires:
// * The first field MUST be a next pointer.
// * There must be a futex, which makes the mutex immovable.
//
// Note: a mutex MUST be unlocked before being freed or unmapped.
struct a0_mtx_s {
a0_mtx_t* next;
a0_mtx_t* prev;
a0_ftx_t ftx;
};
a0_err_t a0_mtx_lock(a0_mtx_t*) A0_WARN_UNUSED_RESULT;
a0_err_t a0_mtx_timedlock(a0_mtx_t*, a0_time_mono_t) A0_WARN_UNUSED_RESULT;
a0_err_t a0_mtx_trylock(a0_mtx_t*) A0_WARN_UNUSED_RESULT;
a0_err_t a0_mtx_consistent(a0_mtx_t*);
a0_err_t a0_mtx_unlock(a0_mtx_t*);
typedef a0_ftx_t a0_cnd_t;
a0_err_t a0_cnd_wait(a0_cnd_t*, a0_mtx_t*);
a0_err_t a0_cnd_timedwait(a0_cnd_t*, a0_mtx_t*, a0_time_mono_t);
a0_err_t a0_cnd_signal(a0_cnd_t*, a0_mtx_t*);
a0_err_t a0_cnd_broadcast(a0_cnd_t*, a0_mtx_t*);
#ifdef __cplusplus
}
#endif
#endif // A0_MTX_H

View File

@ -1,64 +0,0 @@
#include "strconv.h"
#include "a0/err.h"
#include <string.h>
static const char DECIMAL_DIGITS[] =
"00010203040506070809"
"10111213141516171819"
"20212223242526272829"
"30313233343536373839"
"40414243444546474849"
"50515253545556575859"
"60616263646566676869"
"70717273747576777879"
"80818283848586878889"
"90919293949596979899";
a0_err_t a0_u32_to_str(uint32_t val, char* buf_start, char* buf_end, char** start_ptr) {
return a0_u64_to_str(val, buf_start, buf_end, start_ptr);
}
a0_err_t a0_u64_to_str(uint64_t val, char* buf_start, char* buf_end, char** start_ptr) {
uint64_t orig_val = val;
char* ptr = buf_end;
while (val >= 10) {
ptr -= 2;
memcpy(ptr, &DECIMAL_DIGITS[2 * (val % 100)], sizeof(uint16_t));
val /= 100;
}
if (val) {
*--ptr = (char)('0' + val);
}
memset(buf_start, '0', ptr - buf_start);
ptr -= (!orig_val);
if (start_ptr) {
*start_ptr = ptr;
}
return A0_OK;
}
a0_err_t a0_str_to_u32(const char* start, const char* end, uint32_t* out) {
*out = 0;
for (const char* ptr = start; ptr < end; ptr++) {
if (*ptr < '0' || *ptr > '9') {
return A0_ERR_INVALID_ARG;
}
*out *= 10;
*out += *ptr - '0';
}
return A0_OK;
}
a0_err_t a0_str_to_u64(const char* start, const char* end, uint64_t* out) {
*out = 0;
for (const char* ptr = start; ptr < end; ptr++) {
if (*ptr < '0' || *ptr > '9') {
return A0_ERR_INVALID_ARG;
}
*out *= 10;
*out += *ptr - '0';
}
return A0_OK;
}

View File

@ -1,31 +0,0 @@
#ifndef A0_SRC_STRCONV_H
#define A0_SRC_STRCONV_H
#include "a0/err.h"
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
// Converts a uint32 or uint64 to string.
// The entire buffer will be populated, if not with given the value, then with '0'.
// Populates the buffer from the back.
// start_ptr, if not null, will be set to the point within the buffer where the number starts.
// Does NOT check for overflow.
a0_err_t a0_u32_to_str(uint32_t val, char* buf_start, char* buf_end, char** start_ptr);
a0_err_t a0_u64_to_str(uint64_t val, char* buf_start, char* buf_end, char** start_ptr);
// Converts a string to uint32 or uint64.
// The string may have leading '0's.
// Returns A0_ERR_INVALID_ARG if any character is not a digit.
// Does NOT check for overflow.
a0_err_t a0_str_to_u32(const char* start, const char* end, uint32_t* out);
a0_err_t a0_str_to_u64(const char* start, const char* end, uint64_t* out);
#ifdef __cplusplus
}
#endif
#endif // A0_SRC_STRCONV_H

View File

@ -1,10 +0,0 @@
#ifndef A0_THREAD_LOCAL_H
#define A0_THREAD_LOCAL_H
#ifdef __cplusplus
#define A0_THREAD_LOCAL thread_local
#else
#define A0_THREAD_LOCAL _Thread_local
#endif // __cplusplus
#endif // A0_THREAD_LOCAL_H

View File

@ -1,30 +0,0 @@
#include "a0/inline.h"
#include "a0/thread_local.h"
#include "a0/tid.h"
#include <pthread.h>
#include <stddef.h>
#include <stdint.h>
#include <syscall.h>
#include <unistd.h>
A0_THREAD_LOCAL uint32_t a0_tid_cache = 0;
static pthread_once_t a0_tid_reset_atfork_once;
A0_STATIC_INLINE
void a0_tid_reset() {
a0_tid_cache = 0;
}
A0_STATIC_INLINE
void a0_tid_reset_atfork() {
pthread_atfork(NULL, NULL, &a0_tid_reset);
}
uint32_t a0_tid() {
if (!a0_tid_cache) {
a0_tid_cache = syscall(SYS_gettid);
pthread_once(&a0_tid_reset_atfork_once, a0_tid_reset_atfork);
}
return a0_tid_cache;
}

View File

@ -1,16 +0,0 @@
#ifndef A0_TID_H
#define A0_TID_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
uint32_t a0_tid();
#ifdef __cplusplus
}
#endif
#endif // A0_TID_H

View File

@ -1,124 +0,0 @@
#include "a0/empty.h"
#include "a0/err.h"
#include "a0/time.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "clock.h"
#include "err_macro.h"
#include "strconv.h"
const char A0_TIME_MONO[] = "a0_time_mono";
a0_err_t a0_time_mono_now(a0_time_mono_t* out) {
return a0_clock_now(CLOCK_BOOTTIME, &out->ts);
}
a0_err_t a0_time_mono_str(a0_time_mono_t time_mono, char mono_str[20]) {
// Mono time as unsigned integer with up to 20 chars: "18446744072709551615"
uint64_t ns = time_mono.ts.tv_sec * NS_PER_SEC + time_mono.ts.tv_nsec;
mono_str[19] = '\0';
return a0_u64_to_str(ns, mono_str, mono_str + 19, NULL);
}
a0_err_t a0_time_mono_parse(const char mono_str[20], a0_time_mono_t* out) {
uint64_t ns;
A0_RETURN_ERR_ON_ERR(a0_str_to_u64(mono_str, mono_str + 19, &ns));
out->ts.tv_sec = ns / NS_PER_SEC;
out->ts.tv_nsec = ns % NS_PER_SEC;
return A0_OK;
}
a0_err_t a0_time_mono_add(a0_time_mono_t time_mono, int64_t add_nsec, a0_time_mono_t* out) {
return a0_clock_add(time_mono.ts, add_nsec, &out->ts);
}
const char A0_TIME_WALL[] = "a0_time_wall";
a0_err_t a0_time_wall_now(a0_time_wall_t* out) {
A0_RETURN_SYSERR_ON_MINUS_ONE(clock_gettime(CLOCK_REALTIME, &out->ts));
return A0_OK;
}
a0_err_t a0_time_wall_str(a0_time_wall_t wall_time, char wall_str[36]) {
// Wall time in RFC 3999 Nano: "2006-01-02T15:04:05.999999999-07:00"
struct tm wall_tm;
gmtime_r(&wall_time.ts.tv_sec, &wall_tm);
strftime(&wall_str[0], 20, "%Y-%m-%dT%H:%M:%S", &wall_tm);
snprintf(&wall_str[19], 17, ".%09ld-00:00", wall_time.ts.tv_nsec);
wall_str[35] = '\0';
return A0_OK;
}
a0_err_t a0_time_wall_parse(const char wall_str[36], a0_time_wall_t* out) {
// strptime requires _GNU_SOURCE, which we don't want, so we do it our selves.
// Hard code "%Y-%m-%dT%H:%M:%S" + ".%09ld-00:00" pattern.
struct tm wall_tm = A0_EMPTY;
// %Y
A0_RETURN_ERR_ON_ERR(a0_str_to_u32(wall_str + 0, wall_str + 4, (uint32_t*)&wall_tm.tm_year));
wall_tm.tm_year -= 1900;
// -
if (wall_str[4] != '-') {
return A0_ERR_INVALID_ARG;
}
// %m
A0_RETURN_ERR_ON_ERR(a0_str_to_u32(wall_str + 5, wall_str + 7, (uint32_t*)&wall_tm.tm_mon));
if (wall_tm.tm_mon < 1 || wall_tm.tm_mon > 12) {
return A0_ERR_INVALID_ARG;
}
wall_tm.tm_mon--;
// -
if (wall_str[7] != '-') {
return A0_ERR_INVALID_ARG;
}
// %d
A0_RETURN_ERR_ON_ERR(a0_str_to_u32(wall_str + 8, wall_str + 10, (uint32_t*)&wall_tm.tm_mday));
if (wall_tm.tm_mday < 1 || wall_tm.tm_mday > 31) {
return A0_ERR_INVALID_ARG;
}
// T
if (wall_str[10] != 'T') {
return A0_ERR_INVALID_ARG;
}
// %H
A0_RETURN_ERR_ON_ERR(a0_str_to_u32(wall_str + 11, wall_str + 13, (uint32_t*)&wall_tm.tm_hour));
if (wall_tm.tm_hour > 24) {
return A0_ERR_INVALID_ARG;
}
// :
if (wall_str[13] != ':') {
return A0_ERR_INVALID_ARG;
}
// %M
A0_RETURN_ERR_ON_ERR(a0_str_to_u32(wall_str + 14, wall_str + 16, (uint32_t*)&wall_tm.tm_min));
if (wall_tm.tm_min > 60) {
return A0_ERR_INVALID_ARG;
}
// :
if (wall_str[16] != ':') {
return A0_ERR_INVALID_ARG;
}
// %S
A0_RETURN_ERR_ON_ERR(a0_str_to_u32(wall_str + 17, wall_str + 19, (uint32_t*)&wall_tm.tm_sec));
if (wall_tm.tm_sec > 61) {
return A0_ERR_INVALID_ARG;
}
// .
if (wall_str[19] != '.') {
return A0_ERR_INVALID_ARG;
}
if (memcmp(wall_str + 29, "-00:00", 6) != 0) {
return A0_ERR_INVALID_ARG;
}
// Use timegm, cause it's a pain to compute months/years to seconds.
out->ts.tv_sec = timegm(&wall_tm);
return a0_str_to_u64(wall_str + 20, wall_str + 29, (uint64_t*)&out->ts.tv_nsec);
}

View File

@ -1,97 +0,0 @@
/**
* \file time.h
* \rst
*
* Mono Time
* ---------
*
* | Mono time is a number of nanoseconds from machine boottime.
* | This time cannot decrease and duration between ticks is constant.
* | This time is not related to wall clock time.
* | This time is most suitable for measuring durations.
* |
* | As a string, it is represented as a 20 char number:
* | **18446744072709551615**
* |
* | Note that this uses CLOCK_BOOTTIME under the hood, not CLOCK_MONOTONIC.
*
* Wall Time
* ---------
*
* | Wall time is an time object representing human-readable wall clock time.
* | This time can decrease and duration between ticks is not constant.
* | This time is most related to wall clock time.
* | This time is not suitable for measuring durations.
* |
* | As a string, it is represented as a 36 char RFC 3999 Nano / ISO 8601:
* | **2006-01-02T15:04:05.999999999-07:00**
*
* \endrst
*/
#ifndef A0_TIME_H
#define A0_TIME_H
#include "a0/err.h"
#include <stdint.h>
#include <time.h>
#ifdef __cplusplus
extern "C" {
#endif
/** \addtogroup TIME_MONO
* @{
*/
/// Header key for mono timestamps.
extern const char A0_TIME_MONO[];
/// Monotonic timestamp. Despite the name, uses CLOCK_BOOTTIME.
typedef struct a0_time_mono_s {
struct timespec ts;
} a0_time_mono_t;
/// Get the current mono timestamps.
a0_err_t a0_time_mono_now(a0_time_mono_t*);
/// Stringify a given mono timestamps.
a0_err_t a0_time_mono_str(a0_time_mono_t, char mono_str[20]);
/// Parse a stringified mono timestamps.
a0_err_t a0_time_mono_parse(const char mono_str[20], a0_time_mono_t*);
/// Add a duration in nanoseconds to a mono timestamp.
a0_err_t a0_time_mono_add(a0_time_mono_t, int64_t add_nsec, a0_time_mono_t*);
/** @}*/
/** \addtogroup TIME_WALL
* @{
*/
/// Header key for wall timestamps.
extern const char A0_TIME_WALL[];
/// Wall clock timestamp.
typedef struct a0_time_wall_s {
struct timespec ts;
} a0_time_wall_t;
/// Get the current wall timestamps.
a0_err_t a0_time_wall_now(a0_time_wall_t*);
/// Stringify a given wall timestamps.
a0_err_t a0_time_wall_str(a0_time_wall_t, char wall_str[36]);
/// Parse a stringified wall timestamps.
a0_err_t a0_time_wall_parse(const char wall_str[36], a0_time_wall_t*);
/** @}*/
#ifdef __cplusplus
}
#endif
#endif // A0_TRANSPORT_H

View File

@ -1,7 +0,0 @@
#ifndef A0_UNUSED_H
#define A0_UNUSED_H
#define A0_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
#define A0_MAYBE_UNUSED(X) ((void)(X))
#endif // A0_UNUSED_H

View File

@ -1,66 +0,0 @@
#pragma once
#include "libipc/utility/log.h"
#include "libipc/mutex.h"
#include "get_wait_time.h"
#include "sync_obj_impl.h"
#include "a0/err_macro.h"
#include "a0/mtx.h"
namespace ipc {
namespace detail {
namespace sync {
class condition : public sync::obj_impl<a0_cnd_t> {
public:
condition() = default;
~condition() = default;
bool wait(ipc::sync::mutex &mtx, std::uint64_t tm) noexcept {
if (!valid()) return false;
if (tm == invalid_value) {
int eno = A0_SYSERR(a0_cnd_wait(native(), static_cast<a0_mtx_t *>(mtx.native())));
if (eno != 0) {
ipc::error("fail condition wait[%d]\n", eno);
return false;
}
} else {
auto ts = linux_::detail::make_timespec(tm);
int eno = A0_SYSERR(a0_cnd_timedwait(native(), static_cast<a0_mtx_t *>(mtx.native()), {ts}));
if (eno != 0) {
if (eno != ETIMEDOUT) {
ipc::error("fail condition timedwait[%d]: tm = %zd, tv_sec = %ld, tv_nsec = %ld\n",
eno, tm, ts.tv_sec, ts.tv_nsec);
}
return false;
}
}
return true;
}
bool notify(ipc::sync::mutex &mtx) noexcept {
if (!valid()) return false;
int eno = A0_SYSERR(a0_cnd_signal(native(), static_cast<a0_mtx_t *>(mtx.native())));
if (eno != 0) {
ipc::error("fail condition notify[%d]\n", eno);
return false;
}
return true;
}
bool broadcast(ipc::sync::mutex &mtx) noexcept {
if (!valid()) return false;
int eno = A0_SYSERR(a0_cnd_broadcast(native(), static_cast<a0_mtx_t *>(mtx.native())));
if (eno != 0) {
ipc::error("fail condition broadcast[%d]\n", eno);
return false;
}
return true;
}
};
} // namespace sync
} // namespace detail
} // namespace ipc

View File

@ -1,48 +0,0 @@
#pragma once
#include <cstdint>
#include <cinttypes>
#include <system_error>
#include "libipc/utility/log.h"
#include "a0/time.h"
#include "a0/err_macro.h"
namespace ipc {
namespace linux_ {
namespace detail {
inline bool calc_wait_time(timespec &ts, std::uint64_t tm /*ms*/) noexcept {
std::int64_t add_ns = static_cast<std::int64_t>(tm * 1000000ull);
if (add_ns < 0) {
ipc::error("invalid time = " PRIu64 "\n", tm);
return false;
}
a0_time_mono_t now;
int eno = A0_SYSERR(a0_time_mono_now(&now));
if (eno != 0) {
ipc::error("fail get time[%d]\n", eno);
return false;
}
a0_time_mono_t *target = reinterpret_cast<a0_time_mono_t *>(&ts);
if ((eno = A0_SYSERR(a0_time_mono_add(now, add_ns, target))) != 0) {
ipc::error("fail get time[%d]\n", eno);
return false;
}
return true;
}
inline timespec make_timespec(std::uint64_t tm /*ms*/) noexcept(false) {
timespec ts {};
if (!calc_wait_time(ts, tm)) {
ipc::error("fail calc_wait_time: tm = %zd, tv_sec = %ld, tv_nsec = %ld\n",
tm, ts.tv_sec, ts.tv_nsec);
throw std::system_error{static_cast<int>(errno), std::system_category()};
}
return ts;
}
} // namespace detail
} // namespace linux_
} // namespace ipc

View File

@ -1,232 +0,0 @@
#pragma once
#include <cstdint>
#include <system_error>
#include <mutex>
#include <atomic>
#include "libipc/platform/detail.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/shm.h"
#include "get_wait_time.h"
#include "sync_obj_impl.h"
#include "a0/err_macro.h"
#include "a0/mtx.h"
namespace ipc {
namespace detail {
namespace sync {
class robust_mutex : public sync::obj_impl<a0_mtx_t> {
public:
bool lock(std::uint64_t tm) noexcept {
if (!valid()) return false;
for (;;) {
auto ts = linux_::detail::make_timespec(tm);
int eno = A0_SYSERR(
(tm == invalid_value) ? a0_mtx_lock(native())
: a0_mtx_timedlock(native(), {ts}));
switch (eno) {
case 0:
return true;
case ETIMEDOUT:
return false;
case EOWNERDEAD: {
int eno2 = A0_SYSERR(a0_mtx_consistent(native()));
if (eno2 != 0) {
ipc::error("fail mutex lock[%d] -> consistent[%d]\n", eno, eno2);
return false;
}
int eno3 = A0_SYSERR(a0_mtx_unlock(native()));
if (eno3 != 0) {
ipc::error("fail mutex lock[%d] -> unlock[%d]\n", eno, eno3);
return false;
}
}
break; // loop again
default:
ipc::error("fail mutex lock[%d]\n", eno);
return false;
}
}
}
bool try_lock() noexcept(false) {
if (!valid()) return false;
int eno = A0_SYSERR(a0_mtx_timedlock(native(), {linux_::detail::make_timespec(0)}));
switch (eno) {
case 0:
return true;
case ETIMEDOUT:
return false;
case EOWNERDEAD: {
int eno2 = A0_SYSERR(a0_mtx_consistent(native()));
if (eno2 != 0) {
ipc::error("fail mutex try_lock[%d] -> consistent[%d]\n", eno, eno2);
break;
}
int eno3 = A0_SYSERR(a0_mtx_unlock(native()));
if (eno3 != 0) {
ipc::error("fail mutex try_lock[%d] -> unlock[%d]\n", eno, eno3);
break;
}
}
break;
default:
ipc::error("fail mutex try_lock[%d]\n", eno);
break;
}
throw std::system_error{eno, std::system_category()};
}
bool unlock() noexcept {
if (!valid()) return false;
int eno = A0_SYSERR(a0_mtx_unlock(native()));
if (eno != 0) {
ipc::error("fail mutex unlock[%d]\n", eno);
return false;
}
return true;
}
};
class mutex {
robust_mutex *mutex_ = nullptr;
std::atomic<std::int32_t> *ref_ = nullptr;
struct curr_prog {
struct shm_data {
robust_mutex mtx;
std::atomic<std::int32_t> ref;
struct init {
char const *name;
};
shm_data(init arg)
: mtx{}, ref{0} { mtx.open(arg.name); }
};
ipc::map<ipc::string, shm_data> mutex_handles;
std::mutex lock;
static curr_prog &get() {
static curr_prog info;
return info;
}
};
void acquire_mutex(char const *name) {
if (name == nullptr) {
return;
}
auto &info = curr_prog::get();
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
auto it = info.mutex_handles.find(name);
if (it == info.mutex_handles.end()) {
it = info.mutex_handles
.emplace(std::piecewise_construct,
std::forward_as_tuple(name),
std::forward_as_tuple(curr_prog::shm_data::init{name}))
.first;
}
mutex_ = &it->second.mtx;
ref_ = &it->second.ref;
}
template <typename F>
static void release_mutex(ipc::string const &name, F &&clear) {
if (name.empty()) return;
auto &info = curr_prog::get();
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
auto it = info.mutex_handles.find(name);
if (it == info.mutex_handles.end()) {
return;
}
if (clear()) {
info.mutex_handles.erase(it);
}
}
public:
mutex() = default;
~mutex() = default;
static void init() {
// Avoid exception problems caused by static member initialization order.
curr_prog::get();
}
a0_mtx_t const *native() const noexcept {
return valid() ? mutex_->native() : nullptr;
}
a0_mtx_t *native() noexcept {
return valid() ? mutex_->native() : nullptr;
}
bool valid() const noexcept {
return (mutex_ != nullptr) && (ref_ != nullptr) && mutex_->valid();
}
bool open(char const *name) noexcept {
close();
acquire_mutex(name);
if (!valid()) {
return false;
}
ref_->fetch_add(1, std::memory_order_relaxed);
return true;
}
void close() noexcept {
if ((mutex_ != nullptr) && (ref_ != nullptr)) {
if (mutex_->name() != nullptr) {
release_mutex(mutex_->name(), [this] {
return ref_->fetch_sub(1, std::memory_order_relaxed) <= 1;
});
} else mutex_->close();
}
mutex_ = nullptr;
ref_ = nullptr;
}
void clear() noexcept {
if (mutex_ != nullptr) {
if (mutex_->name() != nullptr) {
release_mutex(mutex_->name(), [this] {
mutex_->clear();
return true;
});
} else mutex_->clear();
}
mutex_ = nullptr;
ref_ = nullptr;
}
static void clear_storage(char const *name) noexcept {
if (name == nullptr) return;
release_mutex(name, [] { return true; });
robust_mutex::clear_storage(name);
}
bool lock(std::uint64_t tm) noexcept {
if (!valid()) return false;
return mutex_->lock(tm);
}
bool try_lock() noexcept(false) {
if (!valid()) return false;
return mutex_->try_lock();
}
bool unlock() noexcept {
if (!valid()) return false;
return mutex_->unlock();
}
};
} // namespace sync
} // namespace detail
} // namespace ipc

View File

@ -1,78 +0,0 @@
#pragma once
#include "libipc/utility/log.h"
#include "libipc/shm.h"
#include "a0/empty.h"
namespace ipc {
namespace detail {
namespace sync {
template <typename SyncT>
class obj_impl {
public:
using sync_t = SyncT;
protected:
ipc::shm::handle shm_;
sync_t *h_ = nullptr;
sync_t *acquire_handle(char const *name) {
if (!shm_.acquire(name, sizeof(sync_t))) {
ipc::error("[acquire_handle] fail shm.acquire: %s\n", name);
return nullptr;
}
return static_cast<sync_t *>(shm_.get());
}
public:
obj_impl() = default;
~obj_impl() = default;
sync_t const *native() const noexcept {
return h_;
}
sync_t *native() noexcept {
return h_;
}
char const *name() const noexcept {
return shm_.name();
}
bool valid() const noexcept {
return h_ != nullptr;
}
bool open(char const *name) noexcept {
close();
if ((h_ = acquire_handle(name)) == nullptr) {
return false;
}
if (shm_.ref() > 1) {
return true;
}
*h_ = A0_EMPTY;
return true;
}
void close() noexcept {
shm_.release();
h_ = nullptr;
}
void clear() noexcept {
shm_.clear(); // Make sure the storage is cleaned up.
h_ = nullptr;
}
static void clear_storage(char const *name) noexcept {
ipc::shm::handle::clear_storage(name);
}
};
} // namespace sync
} // namespace detail
} // namespace ipc

View File

@ -1,13 +0,0 @@
#include "libipc/platform/detail.h"
#if defined(IPC_OS_WINDOWS_)
#elif defined(IPC_OS_LINUX_)
#include "libipc/platform/linux/a0/err.c"
#include "libipc/platform/linux/a0/mtx.c"
#include "libipc/platform/linux/a0/strconv.c"
#include "libipc/platform/linux/a0/tid.c"
#include "libipc/platform/linux/a0/time.c"
#elif defined(IPC_OS_QNX_) || defined(IPC_OS_FREEBSD_)
#else/*IPC_OS*/
# error "Unsupported platform."
#endif

View File

@ -1,9 +0,0 @@
#include "libipc/platform/detail.h"
#if defined(IPC_OS_WINDOWS_)
#include "libipc/platform/win/shm_win.cpp"
#elif defined(IPC_OS_LINUX_) || defined(IPC_OS_QNX_) || defined(IPC_OS_FREEBSD_)
#include "libipc/platform/posix/shm_posix.cpp"
#else/*IPC_OS*/
# error "Unsupported platform."
#endif

View File

@ -1,156 +0,0 @@
#pragma once
#include <cstdint>
#include <cstring>
#include <pthread.h>
#include "libipc/utility/log.h"
#include "libipc/utility/scope_guard.h"
#include "libipc/mutex.h"
#include "libipc/shm.h"
#include "get_wait_time.h"
namespace ipc {
namespace detail {
namespace sync {
class condition {
ipc::shm::handle shm_;
pthread_cond_t *cond_ = nullptr;
pthread_cond_t *acquire_cond(char const *name) {
if (!shm_.acquire(name, sizeof(pthread_cond_t))) {
ipc::error("[acquire_cond] fail shm.acquire: %s\n", name);
return nullptr;
}
return static_cast<pthread_cond_t *>(shm_.get());
}
public:
condition() = default;
~condition() = default;
pthread_cond_t const *native() const noexcept {
return cond_;
}
pthread_cond_t *native() noexcept {
return cond_;
}
bool valid() const noexcept {
static const char tmp[sizeof(pthread_cond_t)] {};
return (cond_ != nullptr)
&& (std::memcmp(tmp, cond_, sizeof(pthread_cond_t)) != 0);
}
bool open(char const *name) noexcept {
close();
if ((cond_ = acquire_cond(name)) == nullptr) {
return false;
}
if (shm_.ref() > 1) {
return valid();
}
::pthread_cond_destroy(cond_);
auto finally = ipc::guard([this] { close(); }); // close when failed
// init condition
int eno;
pthread_condattr_t cond_attr;
if ((eno = ::pthread_condattr_init(&cond_attr)) != 0) {
ipc::error("fail pthread_condattr_init[%d]\n", eno);
return false;
}
IPC_UNUSED_ auto guard_cond_attr = guard([&cond_attr] { ::pthread_condattr_destroy(&cond_attr); });
if ((eno = ::pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED)) != 0) {
ipc::error("fail pthread_condattr_setpshared[%d]\n", eno);
return false;
}
*cond_ = PTHREAD_COND_INITIALIZER;
if ((eno = ::pthread_cond_init(cond_, &cond_attr)) != 0) {
ipc::error("fail pthread_cond_init[%d]\n", eno);
return false;
}
finally.dismiss();
return valid();
}
void close() noexcept {
if ((shm_.ref() <= 1) && cond_ != nullptr) {
int eno;
if ((eno = ::pthread_cond_destroy(cond_)) != 0) {
ipc::error("fail pthread_cond_destroy[%d]\n", eno);
}
}
shm_.release();
cond_ = nullptr;
}
void clear() noexcept {
if ((shm_.ref() <= 1) && cond_ != nullptr) {
int eno;
if ((eno = ::pthread_cond_destroy(cond_)) != 0) {
ipc::error("fail pthread_cond_destroy[%d]\n", eno);
}
}
shm_.clear(); // Make sure the storage is cleaned up.
cond_ = nullptr;
}
static void clear_storage(char const *name) noexcept {
ipc::shm::handle::clear_storage(name);
}
bool wait(ipc::sync::mutex &mtx, std::uint64_t tm) noexcept {
if (!valid()) return false;
switch (tm) {
case invalid_value: {
int eno;
if ((eno = ::pthread_cond_wait(cond_, static_cast<pthread_mutex_t *>(mtx.native()))) != 0) {
ipc::error("fail pthread_cond_wait[%d]\n", eno);
return false;
}
}
break;
default: {
auto ts = posix_::detail::make_timespec(tm);
int eno;
if ((eno = ::pthread_cond_timedwait(cond_, static_cast<pthread_mutex_t *>(mtx.native()), &ts)) != 0) {
if (eno != ETIMEDOUT) {
ipc::error("fail pthread_cond_timedwait[%d]: tm = %zd, tv_sec = %ld, tv_nsec = %ld\n",
eno, tm, ts.tv_sec, ts.tv_nsec);
}
return false;
}
}
break;
}
return true;
}
bool notify(ipc::sync::mutex &) noexcept {
if (!valid()) return false;
int eno;
if ((eno = ::pthread_cond_signal(cond_)) != 0) {
ipc::error("fail pthread_cond_signal[%d]\n", eno);
return false;
}
return true;
}
bool broadcast(ipc::sync::mutex &) noexcept {
if (!valid()) return false;
int eno;
if ((eno = ::pthread_cond_broadcast(cond_)) != 0) {
ipc::error("fail pthread_cond_broadcast[%d]\n", eno);
return false;
}
return true;
}
};
} // namespace sync
} // namespace detail
} // namespace ipc

View File

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

View File

@ -1,280 +0,0 @@
#pragma once
#include <cstring>
#include <cassert>
#include <cstdint>
#include <system_error>
#include <mutex>
#include <atomic>
#include <pthread.h>
#include "libipc/platform/detail.h"
#include "libipc/utility/log.h"
#include "libipc/utility/scope_guard.h"
#include "libipc/memory/resource.h"
#include "libipc/shm.h"
#include "get_wait_time.h"
namespace ipc {
namespace detail {
namespace sync {
class mutex {
ipc::shm::handle *shm_ = nullptr;
std::atomic<std::int32_t> *ref_ = nullptr;
pthread_mutex_t *mutex_ = nullptr;
struct curr_prog {
struct shm_data {
ipc::shm::handle shm;
std::atomic<std::int32_t> ref;
struct init {
char const *name;
std::size_t size;
};
shm_data(init arg)
: shm{arg.name, arg.size}, ref{0} {}
};
ipc::map<ipc::string, shm_data> mutex_handles;
std::mutex lock;
static curr_prog &get() {
static curr_prog info;
return info;
}
};
pthread_mutex_t *acquire_mutex(char const *name) {
if (name == nullptr) {
return nullptr;
}
auto &info = curr_prog::get();
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
auto it = info.mutex_handles.find(name);
if (it == info.mutex_handles.end()) {
it = info.mutex_handles
.emplace(std::piecewise_construct,
std::forward_as_tuple(name),
std::forward_as_tuple(curr_prog::shm_data::init{
name, sizeof(pthread_mutex_t)}))
.first;
}
shm_ = &it->second.shm;
ref_ = &it->second.ref;
if (shm_ == nullptr) {
return nullptr;
}
return static_cast<pthread_mutex_t *>(shm_->get());
}
template <typename F>
static void release_mutex(ipc::string const &name, F &&clear) {
if (name.empty()) return;
auto &info = curr_prog::get();
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
auto it = info.mutex_handles.find(name);
if (it == info.mutex_handles.end()) {
return;
}
if (clear()) {
info.mutex_handles.erase(it);
}
}
static pthread_mutex_t const &zero_mem() {
static const pthread_mutex_t tmp{};
return tmp;
}
public:
mutex() = default;
~mutex() = default;
static void init() {
// Avoid exception problems caused by static member initialization order.
zero_mem();
curr_prog::get();
}
pthread_mutex_t const *native() const noexcept {
return mutex_;
}
pthread_mutex_t *native() noexcept {
return mutex_;
}
bool valid() const noexcept {
return (shm_ != nullptr) && (ref_ != nullptr) && (mutex_ != nullptr)
&& (std::memcmp(&zero_mem(), mutex_, sizeof(pthread_mutex_t)) != 0);
}
bool open(char const *name) noexcept {
close();
if ((mutex_ = acquire_mutex(name)) == nullptr) {
return false;
}
auto self_ref = ref_->fetch_add(1, std::memory_order_relaxed);
if (shm_->ref() > 1 || self_ref > 0) {
return valid();
}
::pthread_mutex_destroy(mutex_);
auto finally = ipc::guard([this] { close(); }); // close when failed
// init mutex
int eno;
pthread_mutexattr_t mutex_attr;
if ((eno = ::pthread_mutexattr_init(&mutex_attr)) != 0) {
ipc::error("fail pthread_mutexattr_init[%d]\n", eno);
return false;
}
IPC_UNUSED_ auto guard_mutex_attr = guard([&mutex_attr] { ::pthread_mutexattr_destroy(&mutex_attr); });
if ((eno = ::pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED)) != 0) {
ipc::error("fail pthread_mutexattr_setpshared[%d]\n", eno);
return false;
}
if ((eno = ::pthread_mutexattr_setrobust(&mutex_attr, PTHREAD_MUTEX_ROBUST)) != 0) {
ipc::error("fail pthread_mutexattr_setrobust[%d]\n", eno);
return false;
}
*mutex_ = PTHREAD_MUTEX_INITIALIZER;
if ((eno = ::pthread_mutex_init(mutex_, &mutex_attr)) != 0) {
ipc::error("fail pthread_mutex_init[%d]\n", eno);
return false;
}
finally.dismiss();
return valid();
}
void close() noexcept {
if ((ref_ != nullptr) && (shm_ != nullptr) && (mutex_ != nullptr)) {
if (shm_->name() != nullptr) {
release_mutex(shm_->name(), [this] {
auto self_ref = ref_->fetch_sub(1, std::memory_order_relaxed);
if ((shm_->ref() <= 1) && (self_ref <= 1)) {
// Before destroying the mutex, try to unlock it.
// This is important for robust mutexes on FreeBSD, which maintain
// a per-thread robust list. If we destroy a mutex while it's locked
// or still in the robust list, FreeBSD may encounter dangling pointers
// later, leading to segfaults.
// Only unlock here (when we're the last reference) to avoid
// interfering with other threads that might be using the mutex.
::pthread_mutex_unlock(mutex_);
int eno;
if ((eno = ::pthread_mutex_destroy(mutex_)) != 0) {
ipc::error("fail pthread_mutex_destroy[%d]\n", eno);
}
return true;
}
return false;
});
} else shm_->release();
}
shm_ = nullptr;
ref_ = nullptr;
mutex_ = nullptr;
}
void clear() noexcept {
if ((shm_ != nullptr) && (mutex_ != nullptr)) {
if (shm_->name() != nullptr) {
release_mutex(shm_->name(), [this] {
// Unlock before destroying, same reasoning as in close()
::pthread_mutex_unlock(mutex_);
int eno;
if ((eno = ::pthread_mutex_destroy(mutex_)) != 0) {
ipc::error("fail pthread_mutex_destroy[%d]\n", eno);
}
shm_->clear();
return true;
});
} else shm_->clear();
}
shm_ = nullptr;
ref_ = nullptr;
mutex_ = nullptr;
}
static void clear_storage(char const *name) noexcept {
if (name == nullptr) return;
release_mutex(name, [] { return true; });
ipc::shm::handle::clear_storage(name);
}
bool lock(std::uint64_t tm) noexcept {
if (!valid()) return false;
for (;;) {
auto ts = posix_::detail::make_timespec(tm);
int eno = (tm == invalid_value)
? ::pthread_mutex_lock(mutex_)
: ::pthread_mutex_timedlock(mutex_, &ts);
switch (eno) {
case 0:
return true;
case ETIMEDOUT:
return false;
case EOWNERDEAD: {
// EOWNERDEAD means we have successfully acquired the lock,
// but the previous owner died. We need to make it consistent.
int eno2 = ::pthread_mutex_consistent(mutex_);
if (eno2 != 0) {
ipc::error("fail pthread_mutex_lock[%d], pthread_mutex_consistent[%d]\n", eno, eno2);
return false;
}
// After calling pthread_mutex_consistent(), the mutex is now in a
// consistent state and we hold the lock. Return success.
return true;
}
default:
ipc::error("fail pthread_mutex_lock[%d]\n", eno);
return false;
}
}
}
bool try_lock() noexcept(false) {
if (!valid()) return false;
auto ts = posix_::detail::make_timespec(0);
int eno = ::pthread_mutex_timedlock(mutex_, &ts);
switch (eno) {
case 0:
return true;
case ETIMEDOUT:
return false;
case EOWNERDEAD: {
// EOWNERDEAD means we have successfully acquired the lock,
// but the previous owner died. We need to make it consistent.
int eno2 = ::pthread_mutex_consistent(mutex_);
if (eno2 != 0) {
ipc::error("fail pthread_mutex_timedlock[%d], pthread_mutex_consistent[%d]\n", eno, eno2);
throw std::system_error{eno2, std::system_category()};
}
// After calling pthread_mutex_consistent(), the mutex is now in a
// consistent state and we hold the lock. Return success.
return true;
}
default:
ipc::error("fail pthread_mutex_timedlock[%d]\n", eno);
break;
}
throw std::system_error{eno, std::system_category()};
}
bool unlock() noexcept {
if (!valid()) return false;
int eno;
if ((eno = ::pthread_mutex_unlock(mutex_)) != 0) {
ipc::error("fail pthread_mutex_unlock[%d]\n", eno);
return false;
}
return true;
}
};
} // namespace sync
} // namespace detail
} // namespace ipc

View File

@ -1,133 +0,0 @@
#pragma once
#include <cstdint>
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
#include <errno.h>
#include "libipc/utility/log.h"
#include "libipc/shm.h"
#include "get_wait_time.h"
namespace ipc {
namespace detail {
namespace sync {
class semaphore {
ipc::shm::handle shm_;
sem_t *h_ = SEM_FAILED;
std::string sem_name_; // Store the actual semaphore name used
public:
semaphore() = default;
~semaphore() noexcept = default;
sem_t *native() const noexcept {
return h_;
}
bool valid() const noexcept {
return h_ != SEM_FAILED;
}
bool open(char const *name, std::uint32_t count) noexcept {
close();
if (!shm_.acquire(name, 1)) {
ipc::error("[open_semaphore] fail shm.acquire: %s\n", name);
return false;
}
// POSIX semaphore names must start with "/" on some platforms (e.g., FreeBSD)
// Use a separate namespace for semaphores to avoid conflicts with shm
if (name[0] == '/') {
sem_name_ = std::string(name) + "_sem";
} else {
sem_name_ = std::string("/") + name + "_sem";
}
h_ = ::sem_open(sem_name_.c_str(), O_CREAT, 0666, static_cast<unsigned>(count));
if (h_ == SEM_FAILED) {
ipc::error("fail sem_open[%d]: %s\n", errno, sem_name_.c_str());
return false;
}
return true;
}
void close() noexcept {
if (!valid()) return;
if (::sem_close(h_) != 0) {
ipc::error("fail sem_close[%d]: %s\n", errno);
}
h_ = SEM_FAILED;
if (!sem_name_.empty() && shm_.name() != nullptr) {
if (shm_.release() <= 1) {
if (::sem_unlink(sem_name_.c_str()) != 0) {
ipc::error("fail sem_unlink[%d]: %s, name: %s\n", errno, sem_name_.c_str());
}
}
}
sem_name_.clear();
}
void clear() noexcept {
if (valid()) {
if (::sem_close(h_) != 0) {
ipc::error("fail sem_close[%d]: %s\n", errno);
}
h_ = SEM_FAILED;
}
if (!sem_name_.empty()) {
::sem_unlink(sem_name_.c_str());
sem_name_.clear();
}
shm_.clear(); // Make sure the storage is cleaned up.
}
static void clear_storage(char const *name) noexcept {
// Construct the semaphore name same way as open() does
std::string sem_name;
if (name[0] == '/') {
sem_name = std::string(name) + "_sem";
} else {
sem_name = std::string("/") + name + "_sem";
}
::sem_unlink(sem_name.c_str());
ipc::shm::handle::clear_storage(name);
}
bool wait(std::uint64_t tm) noexcept {
if (!valid()) return false;
if (tm == invalid_value) {
if (::sem_wait(h_) != 0) {
ipc::error("fail sem_wait[%d]: %s\n", errno);
return false;
}
} else {
auto ts = posix_::detail::make_timespec(tm);
if (::sem_timedwait(h_, &ts) != 0) {
if (errno != ETIMEDOUT) {
ipc::error("fail sem_timedwait[%d]: tm = %zd, tv_sec = %ld, tv_nsec = %ld\n",
errno, tm, ts.tv_sec, ts.tv_nsec);
}
return false;
}
}
return true;
}
bool post(std::uint32_t count) noexcept {
if (!valid()) return false;
for (std::uint32_t i = 0; i < count; ++i) {
if (::sem_post(h_) != 0) {
ipc::error("fail sem_post[%d]: %s\n", errno);
return false;
}
}
return true;
}
};
} // namespace sync
} // namespace detail
} // namespace ipc

View File

@ -1,4 +1,5 @@
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/types.h>
@ -21,7 +22,7 @@
namespace {
struct info_t {
std::atomic<std::int32_t> acc_;
std::atomic_size_t acc_;
};
struct id_info_t {
@ -45,18 +46,11 @@ namespace ipc {
namespace shm {
id_t acquire(char const * name, std::size_t size, unsigned mode) {
if (!is_valid_string(name)) {
if (name == nullptr || name[0] == '\0') {
ipc::error("fail acquire: name is empty\n");
return nullptr;
}
// For portable use, a shared memory object should be identified by name of the form /somename.
// see: https://man7.org/linux/man-pages/man3/shm_open.3.html
ipc::string op_name;
if (name[0] == '/') {
op_name = name;
} else {
op_name = ipc::string{"/"} + name;
}
ipc::string op_name = ipc::string{"__IPC_SHM__"} + name;
// Open the object for read-write access.
int flag = O_RDWR;
switch (mode) {
@ -77,15 +71,9 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
S_IRGRP | S_IWGRP |
S_IROTH | S_IWOTH);
if (fd == -1) {
// only open shm not log error when file not exist
if (open != mode || ENOENT != errno) {
ipc::error("fail shm_open[%d]: %s\n", errno, op_name.c_str());
}
ipc::error("fail shm_open[%d]: %s\n", errno, name);
return nullptr;
}
::fchmod(fd, S_IRUSR | S_IWUSR |
S_IRGRP | S_IWGRP |
S_IROTH | S_IWOTH);
auto ii = mem::alloc<id_info_t>();
ii->fd_ = fd;
ii->size_ = size;
@ -93,30 +81,6 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
return ii;
}
std::int32_t get_ref(id_t id) {
if (id == nullptr) {
return 0;
}
auto ii = static_cast<id_info_t*>(id);
if (ii->mem_ == nullptr || ii->size_ == 0) {
return 0;
}
return acc_of(ii->mem_, ii->size_).load(std::memory_order_acquire);
}
void sub_ref(id_t id) {
if (id == nullptr) {
ipc::error("fail sub_ref: invalid id (null)\n");
return;
}
auto ii = static_cast<id_info_t*>(id);
if (ii->mem_ == nullptr || ii->size_ == 0) {
ipc::error("fail sub_ref: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
return;
}
acc_of(ii->mem_, ii->size_).fetch_sub(1, std::memory_order_acq_rel);
}
void * get_mem(id_t id, std::size_t * size) {
if (id == nullptr) {
ipc::error("fail get_mem: invalid id (null)\n");
@ -129,7 +93,7 @@ void * get_mem(id_t id, std::size_t * size) {
}
int fd = ii->fd_;
if (fd == -1) {
ipc::error("fail get_mem: invalid id (fd = -1)\n");
ipc::error("fail to_mem: invalid id (fd = -1)\n");
return nullptr;
}
if (ii->size_ == 0) {
@ -140,7 +104,7 @@ void * get_mem(id_t id, std::size_t * size) {
}
ii->size_ = static_cast<std::size_t>(st.st_size);
if ((ii->size_ <= sizeof(info_t)) || (ii->size_ % sizeof(info_t))) {
ipc::error("fail get_mem: %s, invalid size = %zd\n", ii->name_.c_str(), ii->size_);
ipc::error("fail to_mem: %s, invalid size = %zd\n", ii->name_.c_str(), ii->size_);
return nullptr;
}
}
@ -164,32 +128,26 @@ void * get_mem(id_t id, std::size_t * size) {
return mem;
}
std::int32_t release(id_t id) noexcept {
void release(id_t id) {
if (id == nullptr) {
ipc::error("fail release: invalid id (null)\n");
return -1;
return;
}
std::int32_t ret = -1;
auto ii = static_cast<id_info_t*>(id);
if (ii->mem_ == nullptr || ii->size_ == 0) {
ipc::error("fail release: invalid id (mem = %p, size = %zd), name = %s\n",
ii->mem_, ii->size_, ii->name_.c_str());
ipc::error("fail release: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
}
else if ((ret = acc_of(ii->mem_, ii->size_).fetch_sub(1, std::memory_order_acq_rel)) <= 1) {
else if (acc_of(ii->mem_, ii->size_).fetch_sub(1, std::memory_order_acquire) == 1) {
::munmap(ii->mem_, ii->size_);
if (!ii->name_.empty()) {
int unlink_ret = ::shm_unlink(ii->name_.c_str());
if (unlink_ret == -1) {
ipc::error("fail shm_unlink[%d]: %s\n", errno, ii->name_.c_str());
}
::shm_unlink(ii->name_.c_str());
}
}
else ::munmap(ii->mem_, ii->size_);
mem::free(ii);
return ret;
}
void remove(id_t id) noexcept {
void remove(id_t id) {
if (id == nullptr) {
ipc::error("fail remove: invalid id (null)\n");
return;
@ -198,29 +156,16 @@ void remove(id_t id) noexcept {
auto name = std::move(ii->name_);
release(id);
if (!name.empty()) {
int unlink_ret = ::shm_unlink(name.c_str());
if (unlink_ret == -1) {
ipc::error("fail shm_unlink[%d]: %s\n", errno, name.c_str());
}
::shm_unlink(name.c_str());
}
}
void remove(char const * name) noexcept {
if (!is_valid_string(name)) {
void remove(char const * name) {
if (name == nullptr || name[0] == '\0') {
ipc::error("fail remove: name is empty\n");
return;
}
// For portable use, a shared memory object should be identified by name of the form /somename.
ipc::string op_name;
if (name[0] == '/') {
op_name = name;
} else {
op_name = ipc::string{"/"} + name;
}
int unlink_ret = ::shm_unlink(op_name.c_str());
if (unlink_ret == -1) {
ipc::error("fail shm_unlink[%d]: %s\n", errno, op_name.c_str());
}
::shm_unlink((ipc::string{"__IPC_SHM__"} + name).c_str());
}
} // namespace shm

View File

@ -1,11 +1,6 @@
#if defined(__MINGW32__)
#include <windows.h>
#else
#include <Windows.h>
#endif
#include <atomic>
#include <string>
#include <utility>
@ -14,67 +9,48 @@
#include "libipc/pool_alloc.h"
#include "libipc/utility/log.h"
#include "libipc/platform/to_tchar.h"
#include "libipc/platform/get_sa.h"
#include "libipc/memory/resource.h"
#include "to_tchar.h"
#include "get_sa.h"
namespace {
struct info_t {
std::atomic<std::int32_t> acc_;
};
struct id_info_t {
HANDLE h_ = NULL;
void* mem_ = nullptr;
std::size_t size_ = 0;
};
constexpr std::size_t calc_size(std::size_t size) {
return ((((size - 1) / alignof(info_t)) + 1) * alignof(info_t)) + sizeof(info_t);
}
inline auto& acc_of(void* mem, std::size_t size) {
return reinterpret_cast<info_t*>(static_cast<ipc::byte_t*>(mem) + size - sizeof(info_t))->acc_;
}
} // internal-linkage
namespace ipc {
namespace shm {
id_t acquire(char const * name, std::size_t size, unsigned mode) {
if (!is_valid_string(name)) {
if (name == nullptr || name[0] == '\0') {
ipc::error("fail acquire: name is empty\n");
return nullptr;
}
HANDLE h;
auto fmt_name = ipc::detail::to_tchar(name);
auto fmt_name = ipc::detail::to_tchar(ipc::string{"__IPC_SHM__"} + name);
// Opens a named file mapping object.
if (mode == open) {
h = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, fmt_name.c_str());
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 {
std::size_t alloc_size = calc_size(size);
h = ::CreateFileMapping(INVALID_HANDLE_VALUE, detail::get_sa(), PAGE_READWRITE | SEC_COMMIT,
0, static_cast<DWORD>(alloc_size), fmt_name.c_str());
DWORD err = ::GetLastError();
0, static_cast<DWORD>(size), fmt_name.c_str());
// If the object exists before the function call, the function returns a handle to the existing object
// (with its current size, not the specified size), and GetLastError returns ERROR_ALREADY_EXISTS.
if ((mode == create) && (err == ERROR_ALREADY_EXISTS)) {
if (h != NULL) ::CloseHandle(h);
if ((mode == create) && (::GetLastError() == ERROR_ALREADY_EXISTS)) {
::CloseHandle(h);
h = NULL;
}
if (h == NULL) {
ipc::error("fail CreateFileMapping[%d]: %s\n", static_cast<int>(err), name);
return nullptr;
}
if (h == NULL) {
ipc::error("fail CreateFileMapping/OpenFileMapping[%d]: %s\n", static_cast<int>(::GetLastError()), name);
return nullptr;
}
auto ii = mem::alloc<id_info_t>();
ii->h_ = h;
@ -82,30 +58,6 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
return ii;
}
std::int32_t get_ref(id_t id) {
if (id == nullptr) {
return 0;
}
auto ii = static_cast<id_info_t*>(id);
if (ii->mem_ == nullptr || ii->size_ == 0) {
return 0;
}
return acc_of(ii->mem_, calc_size(ii->size_)).load(std::memory_order_acquire);
}
void sub_ref(id_t id) {
if (id == nullptr) {
ipc::error("fail sub_ref: invalid id (null)\n");
return;
}
auto ii = static_cast<id_info_t*>(id);
if (ii->mem_ == nullptr || ii->size_ == 0) {
ipc::error("fail sub_ref: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
return;
}
acc_of(ii->mem_, calc_size(ii->size_)).fetch_sub(1, std::memory_order_acq_rel);
}
void * get_mem(id_t id, std::size_t * size) {
if (id == nullptr) {
ipc::error("fail get_mem: invalid id (null)\n");
@ -130,42 +82,30 @@ void * get_mem(id_t id, std::size_t * size) {
ipc::error("fail VirtualQuery[%d]\n", static_cast<int>(::GetLastError()));
return nullptr;
}
std::size_t actual_size = static_cast<std::size_t>(mem_info.RegionSize);
if (ii->size_ == 0) {
// Opening existing shared memory
ii->size_ = actual_size - sizeof(info_t);
}
// else: Keep user-requested size (already set in acquire)
ii->mem_ = mem;
ii->size_ = static_cast<std::size_t>(mem_info.RegionSize);
if (size != nullptr) *size = ii->size_;
// Initialize or increment reference counter
acc_of(mem, calc_size(ii->size_)).fetch_add(1, std::memory_order_release);
return static_cast<void *>(mem);
}
std::int32_t release(id_t id) noexcept {
void release(id_t id) {
if (id == nullptr) {
ipc::error("fail release: invalid id (null)\n");
return -1;
return;
}
std::int32_t ret = -1;
auto ii = static_cast<id_info_t*>(id);
if (ii->mem_ == nullptr || ii->size_ == 0) {
ipc::error("fail release: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
}
else {
ret = acc_of(ii->mem_, calc_size(ii->size_)).fetch_sub(1, std::memory_order_acq_rel);
::UnmapViewOfFile(static_cast<LPCVOID>(ii->mem_));
}
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 ret;
}
void remove(id_t id) noexcept {
void remove(id_t id) {
if (id == nullptr) {
ipc::error("fail release: invalid id (null)\n");
return;
@ -173,8 +113,8 @@ void remove(id_t id) noexcept {
release(id);
}
void remove(char const * name) noexcept {
if (!is_valid_string(name)) {
void remove(char const * name) {
if (name == nullptr || name[0] == '\0') {
ipc::error("fail remove: name is empty\n");
return;
}

View File

@ -1,10 +1,6 @@
#pragma once
#if defined(__MINGW32__)
#include <windows.h>
#else
#include <Windows.h>
#endif
#include <type_traits>
#include <string>
@ -15,8 +11,8 @@
#include <cstddef>
#include "libipc/utility/concept.h"
#include "libipc/memory/resource.h"
#include "libipc/platform/detail.h"
#include "libipc/memory/resource.h"
namespace ipc {
namespace detail {
@ -45,8 +41,8 @@ constexpr auto to_tchar(ipc::string &&str) -> IsSameChar<T, ipc::string, ipc::st
}
/**
* \remarks codecvt_utf8_utf16/std::wstring_convert is deprecated
* \see https://codingtidbit.com/2020/02/09/c17-codecvt_utf8-is-deprecated/
* 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

View File

@ -0,0 +1,425 @@
#pragma once
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <semaphore.h>
#include <errno.h>
#include <atomic>
#include <tuple>
#include <utility>
#include <cassert>
#include "libipc/def.h"
#include "libipc/waiter_helper.h"
#include "libipc/utility/log.h"
#include "libipc/platform/detail.h"
#include "libipc/memory/resource.h"
namespace ipc {
namespace detail {
inline static bool calc_wait_time(timespec& ts, std::size_t tm /*ms*/) {
timeval now;
int eno = ::gettimeofday(&now, NULL);
if (eno != 0) {
ipc::error("fail gettimeofday [%d]\n", eno);
return false;
}
ts.tv_nsec = (now.tv_usec + (tm % 1000) * 1000) * 1000;
ts.tv_sec = now.tv_sec + (tm / 1000) + (ts.tv_nsec / 1000000000);
ts.tv_nsec %= 1000000000;
return true;
}
#pragma push_macro("IPC_PTHREAD_FUNC_")
#undef IPC_PTHREAD_FUNC_
#define IPC_PTHREAD_FUNC_(CALL, ...) \
int eno; \
if ((eno = ::CALL(__VA_ARGS__)) != 0) { \
ipc::error("fail " #CALL " [%d]\n", eno); \
return false; \
} \
return true
class mutex {
pthread_mutex_t mutex_ = PTHREAD_MUTEX_INITIALIZER;
public:
pthread_mutex_t& native() {
return mutex_;
}
bool open() {
int eno;
// init mutex
pthread_mutexattr_t mutex_attr;
if ((eno = ::pthread_mutexattr_init(&mutex_attr)) != 0) {
ipc::error("fail pthread_mutexattr_init[%d]\n", eno);
return false;
}
IPC_UNUSED_ auto guard_mutex_attr = unique_ptr(&mutex_attr, ::pthread_mutexattr_destroy);
if ((eno = ::pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED)) != 0) {
ipc::error("fail pthread_mutexattr_setpshared[%d]\n", eno);
return false;
}
if ((eno = ::pthread_mutexattr_setrobust(&mutex_attr, PTHREAD_MUTEX_ROBUST)) != 0) {
ipc::error("fail pthread_mutexattr_setrobust[%d]\n", eno);
return false;
}
if ((eno = ::pthread_mutex_init(&mutex_, &mutex_attr)) != 0) {
ipc::error("fail pthread_mutex_init[%d]\n", eno);
return false;
}
return true;
}
bool close() {
IPC_PTHREAD_FUNC_(pthread_mutex_destroy, &mutex_);
}
bool lock() {
for (;;) {
int eno = ::pthread_mutex_lock(&mutex_);
switch (eno) {
case 0:
return true;
case EOWNERDEAD:
if (::pthread_mutex_consistent(&mutex_) == 0) {
::pthread_mutex_unlock(&mutex_);
break;
}
IPC_FALLTHROUGH_;
case ENOTRECOVERABLE:
if (close() && open()) {
break;
}
IPC_FALLTHROUGH_;
default:
ipc::error("fail pthread_mutex_lock[%d]\n", eno);
return false;
}
}
}
bool unlock() {
IPC_PTHREAD_FUNC_(pthread_mutex_unlock, &mutex_);
}
};
class condition {
pthread_cond_t cond_ = PTHREAD_COND_INITIALIZER;
public:
bool open() {
int eno;
// init condition
pthread_condattr_t cond_attr;
if ((eno = ::pthread_condattr_init(&cond_attr)) != 0) {
ipc::error("fail pthread_condattr_init[%d]\n", eno);
return false;
}
IPC_UNUSED_ auto guard_cond_attr = unique_ptr(&cond_attr, ::pthread_condattr_destroy);
if ((eno = ::pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED)) != 0) {
ipc::error("fail pthread_condattr_setpshared[%d]\n", eno);
return false;
}
if ((eno = ::pthread_cond_init(&cond_, &cond_attr)) != 0) {
ipc::error("fail pthread_cond_init[%d]\n", eno);
return false;
}
return true;
}
bool close() {
IPC_PTHREAD_FUNC_(pthread_cond_destroy, &cond_);
}
bool wait(mutex& mtx, std::size_t tm = invalid_value) {
switch (tm) {
case 0:
return true;
case invalid_value:
IPC_PTHREAD_FUNC_(pthread_cond_wait, &cond_, &mtx.native());
default: {
timespec ts;
if (!calc_wait_time(ts, tm)) {
ipc::error("fail calc_wait_time: tm = %zd, tv_sec = %ld, tv_nsec = %ld\n",
tm, ts.tv_sec, ts.tv_nsec);
return false;
}
int eno;
if ((eno = ::pthread_cond_timedwait(&cond_, &mtx.native(), &ts)) != 0) {
if (eno != ETIMEDOUT) {
ipc::error("fail pthread_cond_timedwait[%d]: tm = %zd, tv_sec = %ld, tv_nsec = %ld\n",
eno, tm, ts.tv_sec, ts.tv_nsec);
}
return false;
}
}
return true;
}
}
bool notify() {
IPC_PTHREAD_FUNC_(pthread_cond_signal, &cond_);
}
bool broadcast() {
IPC_PTHREAD_FUNC_(pthread_cond_broadcast, &cond_);
}
};
#pragma pop_macro("IPC_PTHREAD_FUNC_")
class sem_helper {
public:
using handle_t = sem_t*;
constexpr static handle_t invalid() noexcept {
return SEM_FAILED;
}
static handle_t open(char const * name, long count) {
handle_t sem = ::sem_open(name, O_CREAT, 0666, count);
if (sem == SEM_FAILED) {
ipc::error("fail sem_open[%d]: %s\n", errno, name);
return invalid();
}
return sem;
}
#pragma push_macro("IPC_SEMAPHORE_FUNC_")
#undef IPC_SEMAPHORE_FUNC_
#define IPC_SEMAPHORE_FUNC_(CALL, ...) \
if (::CALL(__VA_ARGS__) != 0) { \
ipc::error("fail " #CALL "[%d]\n", errno); \
return false; \
} \
return true
static bool close(handle_t h) {
if (h == invalid()) return false;
IPC_SEMAPHORE_FUNC_(sem_close, h);
}
static bool destroy(char const * name) {
IPC_SEMAPHORE_FUNC_(sem_unlink, name);
}
static bool post(handle_t h, long count) {
if (h == invalid()) return false;
auto spost = [](handle_t h) {
IPC_SEMAPHORE_FUNC_(sem_post, h);
};
for (long i = 0; i < count; ++i) {
if (!spost(h)) return false;
}
return true;
}
static bool wait(handle_t h, std::size_t tm = invalid_value) {
if (h == invalid()) return false;
switch (tm) {
case 0:
return true;
case invalid_value:
IPC_SEMAPHORE_FUNC_(sem_wait, h);
default: {
timespec ts;
if (!calc_wait_time(ts, tm)) {
ipc::error("fail calc_wait_time: tm = %zd, tv_sec = %ld, tv_nsec = %ld\n",
tm, ts.tv_sec, ts.tv_nsec);
return false;
}
if (::sem_timedwait(h, &ts) != 0) {
if (errno != ETIMEDOUT) {
ipc::error("fail sem_timedwait [%d]: tm = %zd, tv_sec = %ld, tv_nsec = %ld\n",
errno, tm, ts.tv_sec, ts.tv_nsec);
}
return false;
}
}
return true;
}
}
#pragma pop_macro("IPC_SEMAPHORE_FUNC_")
};
class waiter_holder {
public:
using handle_t = std::tuple<
ipc::string,
sem_helper::handle_t /* sema */,
sem_helper::handle_t /* handshake */>;
static handle_t invalid() noexcept {
return std::make_tuple(
ipc::string{},
sem_helper::invalid(),
sem_helper::invalid());
}
private:
using wait_flags = waiter_helper::wait_flags;
using wait_counter = waiter_helper::wait_counter;
mutex lock_;
wait_counter cnt_;
struct contrl {
waiter_holder * me_;
wait_flags * flags_;
handle_t const & h_;
wait_flags & flags() noexcept {
assert(flags_ != nullptr);
return *flags_;
}
wait_counter & counter() noexcept {
return me_->cnt_;
}
auto get_lock() {
return ipc::detail::unique_lock(me_->lock_);
}
bool sema_wait(std::size_t tm) {
return sem_helper::wait(std::get<1>(h_), tm);
}
bool sema_post(long count) {
return sem_helper::post(std::get<1>(h_), count);
}
bool handshake_wait(std::size_t tm) {
return sem_helper::wait(std::get<2>(h_), tm);
}
bool handshake_post(long count) {
return sem_helper::post(std::get<2>(h_), count);
}
};
public:
handle_t open_h(ipc::string && name) {
auto sem = sem_helper::open(("__WAITER_HELPER_SEM__" + name).c_str(), 0);
if (sem == sem_helper::invalid()) {
return invalid();
}
auto han = sem_helper::open(("__WAITER_HELPER_HAN__" + name).c_str(), 0);
if (han == sem_helper::invalid()) {
return invalid();
}
return std::make_tuple(std::move(name), sem, han);
}
void release_h(handle_t const & h) {
sem_helper::close(std::get<2>(h));
sem_helper::close(std::get<1>(h));
}
void close_h(handle_t const & h) {
auto const & name = std::get<0>(h);
sem_helper::destroy(("__WAITER_HELPER_HAN__" + name).c_str());
sem_helper::destroy(("__WAITER_HELPER_SEM__" + name).c_str());
}
bool open() {
return lock_.open();
}
void close() {
lock_.close();
}
template <typename F>
bool wait_if(handle_t const & h, wait_flags * flags, F&& pred, std::size_t tm = invalid_value) {
assert(flags != nullptr);
contrl ctrl { this, flags, h };
class non_mutex {
public:
void lock () noexcept {}
void unlock() noexcept {}
} nm;
return waiter_helper::wait_if(ctrl, nm, std::forward<F>(pred), tm);
}
bool notify(handle_t const & h) {
contrl ctrl { this, nullptr, h };
return waiter_helper::notify(ctrl);
}
bool broadcast(handle_t const & h) {
contrl ctrl { this, nullptr, h };
return waiter_helper::broadcast(ctrl);
}
bool quit_waiting(handle_t const & h, wait_flags * flags) {
assert(flags != nullptr);
contrl ctrl { this, flags, h };
return waiter_helper::quit_waiting(ctrl);
}
};
class waiter {
waiter_holder helper_;
std::atomic<unsigned> opened_ { 0 };
public:
using handle_t = waiter_holder::handle_t;
static handle_t invalid() noexcept {
return waiter_holder::invalid();
}
handle_t open(char const * name) {
if (name == nullptr || name[0] == '\0') {
return invalid();
}
if ((opened_.fetch_add(1, std::memory_order_acq_rel) == 0) && !helper_.open()) {
return invalid();
}
return helper_.open_h(name);
}
void close(handle_t h) {
if (h == invalid()) return;
helper_.release_h(h);
if (opened_.fetch_sub(1, std::memory_order_release) == 1) {
helper_.close_h(h);
helper_.close();
}
}
template <typename F>
bool wait_if(handle_t h, waiter_helper::wait_flags * flags, F && pred, std::size_t tm = invalid_value) {
if (h == invalid()) return false;
return helper_.wait_if(h, flags, std::forward<F>(pred), tm);
}
bool notify(handle_t h) {
if (h == invalid()) return false;
return helper_.notify(h);
}
bool broadcast(handle_t h) {
if (h == invalid()) return false;
return helper_.broadcast(h);
}
bool quit_waiting(handle_t h, waiter_helper::wait_flags * flags) {
if (h == invalid()) return false;
return helper_.quit_waiting(h, flags);
}
};
} // namespace detail
} // namespace ipc

233
src/libipc/platform/waiter_win.h Executable file
View File

@ -0,0 +1,233 @@
#pragma once
#include <Windows.h>
#include <atomic>
#include <utility>
#include <limits>
#include <cassert>
#include "libipc/rw_lock.h"
#include "libipc/pool_alloc.h"
#include "libipc/shm.h"
#include "libipc/waiter_helper.h"
#include "libipc/utility/log.h"
#include "libipc/platform/to_tchar.h"
#include "libipc/platform/get_sa.h"
#include "libipc/platform/detail.h"
#include "libipc/memory/resource.h"
namespace ipc {
namespace detail {
class semaphore {
HANDLE h_ = NULL;
public:
static void remove(char const * /*name*/) {}
bool open(ipc::string && name, long count = 0, long limit = LONG_MAX) {
h_ = ::CreateSemaphore(detail::get_sa(), count, limit, ipc::detail::to_tchar(std::move(name)).c_str());
if (h_ == NULL) {
ipc::error("fail CreateSemaphore[%lu]: %s\n", ::GetLastError(), name.c_str());
return false;
}
return true;
}
void close() {
::CloseHandle(h_);
}
bool wait(std::size_t tm = invalid_value) {
DWORD ret, ms = (tm == invalid_value) ? INFINITE : static_cast<DWORD>(tm);
switch ((ret = ::WaitForSingleObject(h_, ms))) {
case WAIT_OBJECT_0:
return true;
case WAIT_TIMEOUT:
return false;
case WAIT_ABANDONED:
default:
ipc::error("fail WaitForSingleObject[%lu]: 0x%08X\n", ::GetLastError(), ret);
return false;
}
}
bool post(long count = 1) {
if (::ReleaseSemaphore(h_, count, NULL)) {
return true;
}
ipc::error("fail ReleaseSemaphore[%lu]\n", ::GetLastError());
return false;
}
};
class mutex : public semaphore {
using semaphore::wait;
using semaphore::post;
public:
bool open(ipc::string && name) {
return semaphore::open(std::move(name), 1, 1);
}
bool lock () { return semaphore::wait(); }
bool unlock() { return semaphore::post(); }
};
class condition {
using wait_flags = waiter_helper::wait_flags;
using wait_counter = waiter_helper::wait_counter;
mutex lock_;
semaphore sema_, handshake_;
wait_counter * cnt_ = nullptr;
struct contrl {
condition * me_;
wait_flags * flags_;
wait_flags & flags() noexcept {
assert(flags_ != nullptr);
return *flags_;
}
wait_counter & counter() noexcept {
assert(me_->cnt_ != nullptr);
return *(me_->cnt_);
}
auto get_lock() {
return ipc::detail::unique_lock(me_->lock_);
}
bool sema_wait(std::size_t tm) {
return me_->sema_.wait(tm);
}
bool sema_post(long count) {
return me_->sema_.post(count);
}
bool handshake_wait(std::size_t tm) {
return me_->handshake_.wait(tm);
}
bool handshake_post(long count) {
return me_->handshake_.post(count);
}
};
public:
friend bool operator==(condition const & c1, condition const & c2) {
return c1.cnt_ == c2.cnt_;
}
friend bool operator!=(condition const & c1, condition const & c2) {
return !(c1 == c2);
}
static void remove(char const * name) {
semaphore::remove((ipc::string{ "__COND_HAN__" } + name).c_str());
semaphore::remove((ipc::string{ "__COND_SEM__" } + name).c_str());
mutex ::remove((ipc::string{ "__COND_MTX__" } + name).c_str());
}
bool open(ipc::string const & name, wait_counter * cnt) {
if (lock_ .open("__COND_MTX__" + name) &&
sema_ .open("__COND_SEM__" + name) &&
handshake_.open("__COND_HAN__" + name)) {
cnt_ = cnt;
return true;
}
return false;
}
void close() {
handshake_.close();
sema_ .close();
lock_ .close();
}
template <typename Mutex, typename F>
bool wait_if(Mutex & mtx, wait_flags * flags, F && pred, std::size_t tm = invalid_value) {
assert(flags != nullptr);
contrl ctrl { this, flags };
return waiter_helper::wait_if(ctrl, mtx, std::forward<F>(pred), tm);
}
bool notify() {
contrl ctrl { this, nullptr };
return waiter_helper::notify(ctrl);
}
bool broadcast() {
contrl ctrl { this, nullptr };
return waiter_helper::broadcast(ctrl);
}
bool quit_waiting(wait_flags * flags) {
assert(flags != nullptr);
contrl ctrl { this, flags };
return waiter_helper::quit_waiting(ctrl);
}
};
class waiter {
waiter_helper::wait_counter cnt_;
public:
using handle_t = condition;
static handle_t invalid() {
return condition {};
}
handle_t open(char const * name) {
if (name == nullptr || name[0] == '\0') {
return invalid();
}
condition cond;
if (cond.open(name, &cnt_)) {
return cond;
}
return invalid();
}
void close(handle_t& h) {
if (h == invalid()) return;
h.close();
}
template <typename F>
bool wait_if(handle_t& h, waiter_helper::wait_flags * flags, F&& pred, std::size_t tm = invalid_value) {
if (h == invalid()) return false;
class non_mutex {
public:
void lock () noexcept {}
void unlock() noexcept {}
} nm;
return h.wait_if(nm, flags, std::forward<F>(pred), tm);
}
bool notify(handle_t& h) {
if (h == invalid()) return false;
return h.notify();
}
bool broadcast(handle_t& h) {
if (h == invalid()) return false;
return h.broadcast();
}
bool quit_waiting(handle_t& h, waiter_helper::wait_flags * flags) {
if (h == invalid()) return false;
return h.quit_waiting(flags);
}
};
} // namespace detail
} // namespace ipc

View File

@ -0,0 +1,292 @@
#pragma once
#include <type_traits>
#include <atomic>
#include <utility>
#include "libipc/shm.h"
#include "libipc/memory/resource.h"
#include "libipc/platform/detail.h"
#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || \
defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || \
defined(WINCE) || defined(_WIN32_WCE)
#include "libipc/platform/waiter_win.h"
namespace ipc {
namespace detail {
using mutex_impl = ipc::detail::mutex;
using semaphore_impl = ipc::detail::semaphore;
class condition_impl : public ipc::detail::condition {
using base_t = ipc::detail::condition;
ipc::shm::handle cnt_h_;
waiter_helper::wait_flags flags_;
public:
static void remove(char const * name) {
base_t::remove(name);
ipc::string n = name;
ipc::shm::remove((n + "__COND_CNT__" ).c_str());
ipc::shm::remove((n + "__COND_WAIT__").c_str());
}
bool open(char const * name) {
if (cnt_h_ .acquire(
(ipc::string { name } + "__COND_CNT__" ).c_str(),
sizeof(waiter_helper::wait_counter))) {
flags_.is_closed_.store(false, std::memory_order_release);
return base_t::open(name,
static_cast<waiter_helper::wait_counter *>(cnt_h_.get()));
}
return false;
}
void close() {
flags_.is_closed_.store(true, std::memory_order_release);
base_t::quit_waiting(&flags_);
base_t::close();
cnt_h_.release();
}
bool wait(mutex_impl& mtx, std::size_t tm = invalid_value) {
return base_t::wait_if(mtx, &flags_, [] { return true; }, tm);
}
};
} // namespace detail
} // namespace ipc
#else /*!WIN*/
#include "libipc/platform/waiter_linux.h"
namespace ipc {
namespace detail {
template <typename T>
class object_impl {
ipc::shm::handle h_;
struct info_t {
T object_;
std::atomic<unsigned> opened_;
};
public:
static void remove(char const * name) {
{
ipc::shm::handle h { name, sizeof(info_t) };
if (h.valid()) {
auto info = static_cast<info_t*>(h.get());
info->object_.close();
}
}
ipc::shm::remove(name);
}
T& object() {
return static_cast<info_t*>(h_.get())->object_;
}
template <typename... P>
bool open(char const * name, P&&... params) {
if (!h_.acquire(name, sizeof(info_t))) {
return false;
}
auto info = static_cast<info_t*>(h_.get());
if ((info->opened_.fetch_add(1, std::memory_order_acq_rel) == 0) &&
!info->object_.open(std::forward<P>(params)...)) {
return false;
}
return true;
}
void close() {
if (!h_.valid()) return;
auto info = static_cast<info_t*>(h_.get());
if (info->opened_.fetch_sub(1, std::memory_order_release) == 1) {
info->object_.close();
}
h_.release();
}
};
class mutex_impl : public object_impl<ipc::detail::mutex> {
public:
bool lock () { return object().lock (); }
bool unlock() { return object().unlock(); }
};
class condition_impl : public object_impl<ipc::detail::condition> {
public:
bool wait(mutex_impl& mtx, std::size_t tm = invalid_value) {
return object().wait(mtx.object(), tm);
}
bool notify () { return object().notify (); }
bool broadcast() { return object().broadcast(); }
};
class semaphore_impl {
sem_helper::handle_t h_;
ipc::shm::handle opened_; // std::atomic<unsigned>
ipc::string name_;
auto cnt() {
return static_cast<std::atomic<unsigned>*>(opened_.get());
}
public:
static void remove(char const * name) {
sem_helper::destroy((ipc::string{ "__SEMAPHORE_IMPL_SEM__" } + name).c_str());
ipc::shm::remove ((ipc::string{ "__SEMAPHORE_IMPL_CNT__" } + name).c_str());
}
bool open(char const * name, long count) {
name_ = name;
if (!opened_.acquire(("__SEMAPHORE_IMPL_CNT__" + name_).c_str(), sizeof(std::atomic<unsigned>))) {
return false;
}
if ((h_ = sem_helper::open(("__SEMAPHORE_IMPL_SEM__" + name_).c_str(), count)) == sem_helper::invalid()) {
return false;
}
cnt()->fetch_add(1, std::memory_order_acq_rel);
return true;
}
void close() {
if (h_ == sem_helper::invalid()) return;
sem_helper::close(h_);
if (cnt() == nullptr) return;
if (cnt()->fetch_sub(1, std::memory_order_release) == 1) {
sem_helper::destroy(("__SEMAPHORE_IMPL_SEM__" + name_).c_str());
}
opened_.release();
}
bool wait(std::size_t tm = invalid_value) {
return sem_helper::wait(h_, tm);
}
bool post(long count) {
return sem_helper::post(h_, count);
}
};
} // namespace detail
} // namespace ipc
#endif/*!WIN*/
namespace ipc {
namespace detail {
class waiter_wrapper {
public:
using waiter_t = detail::waiter;
private:
waiter_t* w_ = nullptr;
waiter_t::handle_t h_ = waiter_t::invalid();
waiter_helper::wait_flags flags_;
public:
waiter_wrapper() = default;
explicit waiter_wrapper(waiter_t* w) {
attach(w);
}
waiter_wrapper(const waiter_wrapper&) = delete;
waiter_wrapper& operator=(const waiter_wrapper&) = delete;
waiter_t * waiter() { return w_; }
waiter_t const * waiter() const { return w_; }
void attach(waiter_t* w) {
close();
w_ = w;
}
bool valid() const {
return (w_ != nullptr) && (h_ != waiter_t::invalid());
}
bool open(char const * name) {
if (w_ == nullptr) return false;
close();
flags_.is_closed_.store(false, std::memory_order_release);
h_ = w_->open(name);
return valid();
}
void close() {
if (!valid()) return;
flags_.is_closed_.store(true, std::memory_order_release);
quit_waiting();
w_->close(h_);
h_ = waiter_t::invalid();
}
void quit_waiting() {
w_->quit_waiting(h_, &flags_);
}
template <typename F>
bool wait_if(F && pred, std::size_t tm = invalid_value) {
if (!valid()) return false;
return w_->wait_if(h_, &flags_, std::forward<F>(pred), tm);
}
bool notify() {
if (!valid()) return false;
w_->notify(h_);
return true;
}
bool broadcast() {
if (!valid()) return false;
w_->broadcast(h_);
return true;
}
};
} // namespace detail
class waiter : public detail::waiter_wrapper {
shm::handle shm_;
using detail::waiter_wrapper::attach;
public:
waiter() = default;
waiter(char const * name) {
open(name);
}
~waiter() {
close();
}
bool open(char const * name) {
if (name == nullptr || name[0] == '\0') {
return false;
}
close();
if (!shm_.acquire((ipc::string{ "__SHM_WAITER__" } + name).c_str(), sizeof(waiter_t))) {
return false;
}
attach(static_cast<waiter_t*>(shm_.get()));
return detail::waiter_wrapper::open((ipc::string{ "__IMP_WAITER__" } + name).c_str());
}
void close() {
detail::waiter_wrapper::close();
shm_.release();
}
};
} // namespace ipc

View File

@ -1,133 +0,0 @@
#pragma once
#include <cstdint>
#include <string>
#include <mutex>
#if defined(__MINGW32__)
#include <windows.h>
#else
#include <Windows.h>
#endif
#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();
}
void clear() noexcept {
close();
}
static void clear_storage(char const *name) noexcept {
ipc::shm::handle::clear_storage(name);
ipc::sync::mutex::clear_storage(name);
ipc::sync::semaphore::clear_storage(name);
}
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

@ -1,109 +0,0 @@
#pragma once
#include <cstdint>
#include <system_error>
#if defined(__MINGW32__)
#include <windows.h>
#else
#include <Windows.h>
#endif
#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;
}
void clear() noexcept {
close();
}
static void clear_storage(char const */*name*/) noexcept {
}
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

@ -1,85 +0,0 @@
#pragma once
#include <cstdint>
#if defined(__MINGW32__)
#include <windows.h>
#else
#include <Windows.h>
#endif
#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;
}
void clear() noexcept {
close();
}
static void clear_storage(char const */*name*/) noexcept {
}
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

@ -18,7 +18,6 @@
#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 {
@ -30,7 +29,7 @@ protected:
template <typename Elems>
Elems* open(char const * name) {
if (!is_valid_string(name)) {
if (name == nullptr || name[0] == '\0') {
ipc::error("fail open waiter: name is empty!\n");
return nullptr;
}
@ -55,17 +54,8 @@ public:
queue_conn(const queue_conn&) = delete;
queue_conn& operator=(const queue_conn&) = delete;
void clear() noexcept {
elems_h_.clear();
}
static void clear_storage(char const *name) noexcept {
shm::handle::clear_storage(name);
}
template <typename Elems>
bool connected(Elems* elems) const noexcept {
return elems->connected(connected_);
bool connected() const noexcept {
return connected_ != 0;
}
circ::cc_t connected_id() const noexcept {
@ -78,16 +68,16 @@ public:
-> std::tuple<bool, bool, decltype(std::declval<Elems>().cursor())> {
if (elems == nullptr) return {};
// if it's already connected, just return
if (connected(elems)) return {connected(elems), false, 0};
if (connected()) return {connected(), false, 0};
connected_ = elems->connect_receiver();
return {connected(elems), true, elems->cursor()};
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(elems)) return false;
if (!connected()) return false;
elems->disconnect_receiver(std::exchange(connected_, 0));
return true;
}
@ -113,7 +103,7 @@ public:
explicit queue_base(char const * name)
: queue_base{} {
elems_ = queue_conn::template open<elems_t>(name);
elems_ = open<elems_t>(name);
}
explicit queue_base(elems_t * elems) noexcept
@ -126,17 +116,6 @@ public:
base_t::close();
}
bool open(char const * name) noexcept {
base_t::close();
elems_ = queue_conn::template open<elems_t>(name);
return elems_ != nullptr;
}
void clear() noexcept {
base_t::clear();
elems_ = nullptr;
}
elems_t * elems() noexcept { return elems_; }
elems_t const * elems() const noexcept { return elems_; }
@ -151,10 +130,6 @@ public:
elems_->disconnect_sender();
}
bool connected() const noexcept {
return base_t::connected(elems_);
}
bool connect() noexcept {
auto tp = base_t::connect(elems_);
if (std::get<0>(tp) && std::get<1>(tp)) {
@ -169,7 +144,7 @@ public:
}
std::size_t conn_count() const noexcept {
return (elems_ == nullptr) ? static_cast<std::size_t>(invalid_value) : elems_->conn_count();
return (elems_ == nullptr) ? invalid_value : elems_->conn_count();
}
bool valid() const noexcept {

View File

@ -1,85 +0,0 @@
#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_) || defined(IPC_OS_FREEBSD_)
#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();
}
void condition::clear() noexcept {
impl(p_)->cond_.clear();
}
void condition::clear_storage(char const * name) noexcept {
ipc::detail::sync::condition::clear_storage(name);
}
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

@ -1,85 +0,0 @@
#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_) || defined(IPC_OS_FREEBSD_)
#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();
}
void mutex::clear() noexcept {
impl(p_)->lock_.clear();
}
void mutex::clear_storage(char const * name) noexcept {
ipc::detail::sync::mutex::clear_storage(name);
}
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

@ -1,79 +0,0 @@
#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_) || defined(IPC_OS_FREEBSD_)
#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();
}
void semaphore::clear() noexcept {
impl(p_)->sem_.clear();
}
void semaphore::clear_storage(char const * name) noexcept {
ipc::detail::sync::semaphore::clear_storage(name);
}
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

@ -1,22 +0,0 @@
#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_) || defined(IPC_OS_FREEBSD_)
#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

@ -3,7 +3,6 @@
#include <new>
#include <utility>
#include "libipc/platform/detail.h"
#include "libipc/utility/concept.h"
#include "libipc/pool_alloc.h"
@ -18,45 +17,49 @@ 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> {
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> {
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> {
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> {
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> {
constexpr auto clear_impl(T* p) -> IsImplUncomfortable<T, void> {
mem::free(p);
}
template <typename T>
IPC_CONSTEXPR_ auto impl(T* const (& p)) -> IsImplUncomfortable<T> {
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) {
constexpr static T* make(P&&... params) {
return make_impl<T>(std::forward<P>(params)...);
}
IPC_CONSTEXPR_ void clear() {
#if __cplusplus >= 201703L
constexpr void clear() {
#else /*__cplusplus < 201703L*/
void clear() {
#endif/*__cplusplus < 201703L*/
clear_impl(static_cast<T*>(const_cast<pimpl*>(this)));
}
};

View File

@ -3,7 +3,6 @@
#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"
@ -45,15 +44,13 @@ enum {
};
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 {
T horrible_cast(U val) {
union {
T t;
U u;
} r = {};
r.u = rhs;
return r.t;
T out;
U in;
} u;
u.in = val;
return u.out;
}
IPC_CONSTEXPR_ std::size_t make_align(std::size_t align, std::size_t size) {

View File

@ -1,97 +0,0 @@
#pragma once
#include <utility>
#include <string>
#include <mutex>
#include <atomic>
#include "libipc/def.h"
#include "libipc/mutex.h"
#include "libipc/condition.h"
#include "libipc/platform/detail.h"
namespace ipc {
namespace detail {
class waiter {
ipc::sync::condition cond_;
ipc::sync::mutex lock_;
std::atomic<bool> quit_ {false};
public:
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();
}
void clear() noexcept {
cond_.clear();
lock_.clear();
}
static void clear_storage(char const *name) noexcept {
ipc::sync::condition::clear_storage((std::string{name} + "_WAITER_COND_").c_str());
ipc::sync::mutex::clear_storage((std::string{name} + "_WAITER_LOCK_").c_str());
}
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

129
src/libipc/waiter_helper.h Normal file
View File

@ -0,0 +1,129 @@
#pragma once
#include <atomic>
#include <limits>
#include <utility>
#include "libipc/def.h"
#include "libipc/utility/scope_guard.h"
namespace ipc {
namespace detail {
struct waiter_helper {
struct wait_counter {
std::atomic<unsigned> waiting_ { 0 };
long counter_ = 0;
};
struct wait_flags {
std::atomic<bool> is_waiting_ { false };
std::atomic<bool> is_closed_ { true };
std::atomic<bool> need_dest_ { false };
};
template <typename Mutex, typename Ctrl, typename F>
static bool wait_if(Ctrl & ctrl, Mutex & mtx, F && pred, std::size_t tm) {
auto & flags = ctrl.flags();
if (flags.is_closed_.load(std::memory_order_acquire)) {
return false;
}
auto & counter = ctrl.counter();
counter.waiting_.fetch_add(1, std::memory_order_release);
flags.is_waiting_.store(true, std::memory_order_relaxed);
auto finally = ipc::guard([&counter, &flags] {
counter.waiting_.fetch_sub(1, std::memory_order_release);
flags.is_waiting_.store(false, std::memory_order_relaxed);
});
{
IPC_UNUSED_ auto guard = ctrl.get_lock();
if (!std::forward<F>(pred)()) return true;
counter.counter_ += 1;
}
mtx.unlock();
bool ret = false;
do {
bool is_waiting = flags.is_waiting_.load(std::memory_order_relaxed);
bool is_closed = flags.is_closed_ .load(std::memory_order_acquire);
if (!is_waiting || is_closed) {
flags.need_dest_.store(false, std::memory_order_release);
ret = false;
break;
}
else if (flags.need_dest_.exchange(false, std::memory_order_release)) {
ret = false;
ctrl.sema_wait(default_timeout);
break;
}
else {
ret = ctrl.sema_wait(tm);
}
} while (flags.need_dest_.load(std::memory_order_acquire));
finally.do_exit();
ret = ctrl.handshake_post(1) && ret;
mtx.lock();
return ret;
}
template <typename Ctrl>
static bool notify(Ctrl & ctrl) {
auto & counter = ctrl.counter();
if ((counter.waiting_.load(std::memory_order_acquire)) == 0) {
return true;
}
bool ret = true;
IPC_UNUSED_ auto guard = ctrl.get_lock();
if (counter.counter_ > 0) {
ret = ctrl.sema_post(1);
counter.counter_ -= 1;
ret = ret && ctrl.handshake_wait(default_timeout);
}
return ret;
}
template <typename Ctrl>
static bool broadcast(Ctrl & ctrl) {
auto & counter = ctrl.counter();
if ((counter.waiting_.load(std::memory_order_acquire)) == 0) {
return true;
}
bool ret = true;
IPC_UNUSED_ auto guard = ctrl.get_lock();
if (counter.counter_ > 0) {
ret = ctrl.sema_post(counter.counter_);
do {
counter.counter_ -= 1;
ret = ret && ctrl.handshake_wait(default_timeout);
} while (counter.counter_ > 0);
}
return ret;
}
template <typename Ctrl>
static bool quit_waiting(Ctrl & ctrl) {
auto & flags = ctrl.flags();
flags.need_dest_.store(true, std::memory_order_relaxed);
if (!flags.is_waiting_.exchange(false, std::memory_order_release)) {
return true;
}
auto & counter = ctrl.counter();
if ((counter.waiting_.load(std::memory_order_acquire)) == 0) {
return true;
}
bool ret = true;
IPC_UNUSED_ auto guard = ctrl.get_lock();
if (counter.counter_ > 0) {
ret = ctrl.sema_post(counter.counter_);
counter.counter_ -= 1;
ret = ret && ctrl.handshake_wait(default_timeout);
}
return ret;
}
};
} // namespace detail
} // namespace ipc

71
src/libipc/waiter_template.inc Executable file
View File

@ -0,0 +1,71 @@
#undef IPC_OBJECT_TYPE_P_
#undef IPC_OBJECT_TYPE_I_
#define IPC_OBJECT_TYPE_P_ IPC_PP_JOIN_(IPC_OBJECT_TYPE_, _)
#define IPC_OBJECT_TYPE_I_ IPC_PP_JOIN_(IPC_OBJECT_TYPE_, _impl)
class IPC_OBJECT_TYPE_::IPC_OBJECT_TYPE_P_ : public pimpl<IPC_OBJECT_TYPE_P_> {
public:
std::string n_;
ipc::detail::IPC_OBJECT_TYPE_I_ h_;
};
void IPC_OBJECT_TYPE_::remove(char const * name) {
detail::IPC_OBJECT_TYPE_I_::remove(name);
}
IPC_OBJECT_TYPE_::IPC_OBJECT_TYPE_()
: p_(p_->make()) {
}
IPC_OBJECT_TYPE_::IPC_OBJECT_TYPE_(char const * name)
: IPC_OBJECT_TYPE_() {
open(name);
}
IPC_OBJECT_TYPE_::IPC_OBJECT_TYPE_(IPC_OBJECT_TYPE_&& rhs)
: IPC_OBJECT_TYPE_() {
swap(rhs);
}
IPC_OBJECT_TYPE_::~IPC_OBJECT_TYPE_() {
close();
p_->clear();
}
void IPC_OBJECT_TYPE_::swap(IPC_OBJECT_TYPE_& rhs) {
std::swap(p_, rhs.p_);
}
IPC_OBJECT_TYPE_& IPC_OBJECT_TYPE_::operator=(IPC_OBJECT_TYPE_ rhs) {
swap(rhs);
return *this;
}
bool IPC_OBJECT_TYPE_::valid() const {
return (p_ != nullptr) && !impl(p_)->n_.empty();
}
char const * IPC_OBJECT_TYPE_::name() const {
return impl(p_)->n_.c_str();
}
bool IPC_OBJECT_TYPE_::open(char const * name IPC_OBJECT_TYPE_OPEN_PARS_) {
if (name == nullptr || name[0] == '\0') {
return false;
}
if (impl(p_)->n_ == name) return true;
close();
if (impl(p_)->h_.open(name IPC_OBJECT_TYPE_OPEN_ARGS_)) {
impl(p_)->n_ = name;
return true;
}
return false;
}
void IPC_OBJECT_TYPE_::close() {
if (!valid()) return;
impl(p_)->h_.close();
impl(p_)->n_.clear();
}

View File

@ -5,11 +5,11 @@
namespace ipc {
namespace mem {
void* pool_alloc::alloc(std::size_t size) noexcept {
void* pool_alloc::alloc(std::size_t size) {
return async_pool_alloc::alloc(size);
}
void pool_alloc::free(void* p, std::size_t size) noexcept {
void pool_alloc::free(void* p, std::size_t size) {
async_pool_alloc::free(p, size);
}

View File

@ -5,7 +5,6 @@
#include "libipc/shm.h"
#include "libipc/utility/pimpl.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
namespace ipc {
@ -48,61 +47,28 @@ handle& handle::operator=(handle rhs) {
return *this;
}
bool handle::valid() const noexcept {
bool handle::valid() const {
return impl(p_)->m_ != nullptr;
}
std::size_t handle::size() const noexcept {
std::size_t handle::size() const {
return impl(p_)->s_;
}
char const * handle::name() const noexcept {
char const * handle::name() const {
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();
const auto id = shm::acquire(name, size, mode);
if (!id) {
return false;
}
impl(p_)->id_ = id;
impl(p_)->n_ = name;
impl(p_)->id_ = shm::acquire((impl(p_)->n_ = name).c_str(), size, mode);
impl(p_)->m_ = shm::get_mem(impl(p_)->id_, &(impl(p_)->s_));
return valid();
}
std::int32_t handle::release() {
if (impl(p_)->id_ == nullptr) return -1;
return shm::release(detach());
}
void handle::clear() noexcept {
void handle::release() {
if (impl(p_)->id_ == nullptr) return;
shm::remove(detach());
}
void handle::clear_storage(char const * name) noexcept {
if (name == nullptr) {
return;
}
shm::remove(name);
shm::release(detach());
}
void* handle::get() const {

77
src/waiter.cpp Executable file
View File

@ -0,0 +1,77 @@
#include <string>
#include "libipc/waiter.h"
#include "libipc/utility/pimpl.h"
#include "libipc/platform/waiter_wrapper.h"
#undef IPC_PP_CAT_
#undef IPC_PP_JOIN_T__
#undef IPC_PP_JOIN_
#define IPC_PP_CAT_(X, ...) X##__VA_ARGS__
#define IPC_PP_JOIN_T__(X, ...) IPC_PP_CAT_(X, __VA_ARGS__)
#define IPC_PP_JOIN_(X, ...) IPC_PP_JOIN_T__(X, __VA_ARGS__)
namespace ipc {
#undef IPC_OBJECT_TYPE_
#undef IPC_OBJECT_TYPE_OPEN_PARS_
#undef IPC_OBJECT_TYPE_OPEN_ARGS_
#define IPC_OBJECT_TYPE_ mutex
#define IPC_OBJECT_TYPE_OPEN_PARS_
#define IPC_OBJECT_TYPE_OPEN_ARGS_
#include "libipc/waiter_template.inc"
bool mutex::lock() {
return impl(p_)->h_.lock();
}
bool mutex::unlock() {
return impl(p_)->h_.unlock();
}
#undef IPC_OBJECT_TYPE_
#undef IPC_OBJECT_TYPE_OPEN_PARS_
#undef IPC_OBJECT_TYPE_OPEN_ARGS_
#define IPC_OBJECT_TYPE_ semaphore
#define IPC_OBJECT_TYPE_OPEN_PARS_ , long count
#define IPC_OBJECT_TYPE_OPEN_ARGS_ , count
#include "libipc/waiter_template.inc"
bool semaphore::wait(std::size_t tm) {
return impl(p_)->h_.wait(tm);
}
bool semaphore::post(long count) {
return impl(p_)->h_.post(count);
}
#undef IPC_OBJECT_TYPE_
#undef IPC_OBJECT_TYPE_OPEN_PARS_
#undef IPC_OBJECT_TYPE_OPEN_ARGS_
#define IPC_OBJECT_TYPE_ condition
#define IPC_OBJECT_TYPE_OPEN_PARS_
#define IPC_OBJECT_TYPE_OPEN_ARGS_
#include "libipc/waiter_template.inc"
bool condition::wait(mutex& mtx, std::size_t tm) {
return impl(p_)->h_.wait(impl(mtx.p_)->h_, tm);
}
bool condition::notify() {
return impl(p_)->h_.notify();
}
bool condition::broadcast() {
return impl(p_)->h_.broadcast();
}
} // namespace ipc

11
test/CMakeLists.txt Normal file → Executable file
View File

@ -15,15 +15,8 @@ include_directories(
${LIBIPC_PROJECT_DIR}/3rdparty
${LIBIPC_PROJECT_DIR}/3rdparty/gtest/include)
# Collect only new test files (exclude archive directory)
file(GLOB SRC_FILES
${LIBIPC_PROJECT_DIR}/test/test_*.cpp
)
file(GLOB HEAD_FILES ${LIBIPC_PROJECT_DIR}/test/test_*.h)
# Ensure we don't include archived tests
list(FILTER SRC_FILES EXCLUDE REGEX "archive")
list(FILTER HEAD_FILES EXCLUDE REGEX "archive")
file(GLOB SRC_FILES ${LIBIPC_PROJECT_DIR}/test/*.cpp)
file(GLOB HEAD_FILES ${LIBIPC_PROJECT_DIR}/test/*.h)
add_executable(${PROJECT_NAME} ${SRC_FILES} ${HEAD_FILES})

View File

@ -1,28 +0,0 @@
project(test-ipc)
if(NOT MSVC)
add_compile_options(
-Wno-attributes
-Wno-missing-field-initializers
-Wno-unused-variable
-Wno-unused-function)
endif()
include_directories(
${LIBIPC_PROJECT_DIR}/include
${LIBIPC_PROJECT_DIR}/src
${LIBIPC_PROJECT_DIR}/test
${LIBIPC_PROJECT_DIR}/3rdparty
${LIBIPC_PROJECT_DIR}/3rdparty/gtest/include)
file(GLOB SRC_FILES
${LIBIPC_PROJECT_DIR}/test/*.cpp
# ${LIBIPC_PROJECT_DIR}/test/profiler/*.cpp
)
file(GLOB HEAD_FILES ${LIBIPC_PROJECT_DIR}/test/*.h)
add_executable(${PROJECT_NAME} ${SRC_FILES} ${HEAD_FILES})
link_directories(${LIBIPC_PROJECT_DIR}/3rdparty/gperftools)
target_link_libraries(${PROJECT_NAME} gtest gtest_main ipc)
#target_link_libraries(${PROJECT_NAME} tcmalloc_minimal)

View File

@ -1,134 +0,0 @@
#include <cstring>
#include <cstdint>
#include <thread>
#include "libipc/shm.h"
#include "test.h"
using namespace ipc::shm;
namespace {
TEST(SHM, acquire) {
handle shm_hd;
EXPECT_FALSE(shm_hd.valid());
EXPECT_TRUE(shm_hd.acquire("my-test-1", 1024));
EXPECT_TRUE(shm_hd.valid());
EXPECT_STREQ(shm_hd.name(), "my-test-1");
EXPECT_TRUE(shm_hd.acquire("my-test-2", 2048));
EXPECT_TRUE(shm_hd.valid());
EXPECT_STREQ(shm_hd.name(), "my-test-2");
EXPECT_TRUE(shm_hd.acquire("my-test-3", 4096));
EXPECT_TRUE(shm_hd.valid());
EXPECT_STREQ(shm_hd.name(), "my-test-3");
}
TEST(SHM, release) {
handle shm_hd;
EXPECT_FALSE(shm_hd.valid());
shm_hd.release();
EXPECT_FALSE(shm_hd.valid());
EXPECT_TRUE(shm_hd.acquire("release-test-1", 512));
EXPECT_TRUE(shm_hd.valid());
shm_hd.release();
EXPECT_FALSE(shm_hd.valid());
}
TEST(SHM, get) {
handle shm_hd;
EXPECT_TRUE(shm_hd.get() == nullptr);
EXPECT_TRUE(shm_hd.acquire("get-test", 2048));
auto mem = shm_hd.get();
EXPECT_TRUE(mem != nullptr);
EXPECT_TRUE(mem == shm_hd.get());
std::uint8_t buf[1024] = {};
EXPECT_TRUE(memcmp(mem, buf, sizeof(buf)) == 0);
handle shm_other(shm_hd.name(), shm_hd.size());
EXPECT_TRUE(shm_other.get() != shm_hd.get());
}
TEST(SHM, hello) {
handle shm_hd;
EXPECT_TRUE(shm_hd.acquire("hello-test", 128));
auto mem = shm_hd.get();
EXPECT_TRUE(mem != nullptr);
constexpr char hello[] = "hello!";
std::memcpy(mem, hello, sizeof(hello));
EXPECT_STREQ((char const *)shm_hd.get(), hello);
shm_hd.release();
EXPECT_TRUE(shm_hd.get() == nullptr);
EXPECT_TRUE(shm_hd.acquire("hello-test", 1024));
mem = shm_hd.get();
EXPECT_TRUE(mem != nullptr);
std::uint8_t buf[1024] = {};
EXPECT_TRUE(memcmp(mem, buf, sizeof(buf)) == 0);
std::memcpy(mem, hello, sizeof(hello));
EXPECT_STREQ((char const *)shm_hd.get(), hello);
}
TEST(SHM, mt) {
handle shm_hd;
EXPECT_TRUE(shm_hd.acquire("mt-test", 256));
constexpr char hello[] = "hello!";
std::memcpy(shm_hd.get(), hello, sizeof(hello));
std::thread {
[&shm_hd] {
handle shm_mt(shm_hd.name(), shm_hd.size());
shm_hd.release();
constexpr char hello[] = "hello!";
EXPECT_STREQ((char const *)shm_mt.get(), hello);
}
}.join();
EXPECT_TRUE(shm_hd.get() == nullptr);
EXPECT_FALSE(shm_hd.valid());
EXPECT_TRUE(shm_hd.acquire("mt-test", 1024));
std::uint8_t buf[1024] = {};
EXPECT_TRUE(memcmp(shm_hd.get(), buf, sizeof(buf)) == 0);
}
TEST(SHM, remove) {
{
auto id = ipc::shm::acquire("hello-remove", 111);
EXPECT_TRUE(ipc_ut::expect_exist("hello-remove", true));
ipc::shm::remove(id);
EXPECT_TRUE(ipc_ut::expect_exist("hello-remove", false));
}
{
auto id = ipc::shm::acquire("hello-remove", 111);
EXPECT_TRUE(ipc_ut::expect_exist("hello-remove", true));
ipc::shm::release(id);
EXPECT_TRUE(ipc_ut::expect_exist("hello-remove", true));
ipc::shm::remove("hello-remove");
EXPECT_TRUE(ipc_ut::expect_exist("hello-remove", false));
}
{
handle shm_hd;
EXPECT_TRUE(shm_hd.acquire("mt-test", 256));
EXPECT_TRUE(ipc_ut::expect_exist("mt-test", true));
shm_hd.clear();
EXPECT_TRUE(ipc_ut::expect_exist("mt-test", false));
}
{
handle shm_hd;
EXPECT_TRUE(shm_hd.acquire("mt-test", 256));
EXPECT_TRUE(ipc_ut::expect_exist("mt-test", true));
shm_hd.clear_storage("mt-test");
EXPECT_TRUE(ipc_ut::expect_exist("mt-test", false));
}
}
} // internal-linkage

View File

@ -1,212 +0,0 @@
#include <thread>
#include <iostream>
#include <mutex>
#include <chrono>
#include <deque>
#include <array>
#include <cstdio>
#include "test.h"
#include "libipc/platform/detail.h"
#if defined(IPC_OS_LINUX_)
#include <pthread.h>
#include <time.h>
TEST(PThread, Robust) {
pthread_mutexattr_t ma;
pthread_mutexattr_init(&ma);
pthread_mutexattr_setpshared(&ma, PTHREAD_PROCESS_SHARED);
pthread_mutexattr_setrobust(&ma, PTHREAD_MUTEX_ROBUST);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_init(&mutex, &ma);
std::thread{[&mutex] {
pthread_mutex_lock(&mutex);
// pthread_mutex_unlock(&mutex);
}}.join();
struct timespec tout;
clock_gettime(CLOCK_REALTIME, &tout);
int r = pthread_mutex_timedlock(&mutex, &tout);
EXPECT_EQ(r, EOWNERDEAD);
pthread_mutex_consistent(&mutex);
pthread_mutex_unlock(&mutex);
pthread_mutex_destroy(&mutex);
}
#elif defined(IPC_OS_WINDOWS_)
#if defined(__MINGW32__)
#include <windows.h>
#else
#include <Windows.h>
#endif
#include <tchar.h>
TEST(PThread, Robust) {
HANDLE lock = CreateMutex(NULL, FALSE, _T("test-robust"));
std::thread{[] {
HANDLE lock = CreateMutex(NULL, FALSE, _T("test-robust"));
WaitForSingleObject(lock, 0);
}}.join();
DWORD r = WaitForSingleObject(lock, 0);
EXPECT_EQ(r, WAIT_ABANDONED);
CloseHandle(lock);
}
#endif // OS
#include "libipc/mutex.h"
TEST(Sync, Mutex) {
ipc::sync::mutex lock;
EXPECT_TRUE(lock.open("test-mutex-robust"));
std::thread{[] {
ipc::sync::mutex lock {"test-mutex-robust"};
EXPECT_TRUE(lock.valid());
EXPECT_TRUE(lock.lock());
}}.join();
EXPECT_THROW(lock.try_lock(), std::system_error);
// int i = 0;
// EXPECT_TRUE(lock.lock());
// i = 100;
// auto t2 = std::thread{[&i] {
// ipc::sync::mutex lock {"test-mutex-robust"};
// EXPECT_TRUE(lock.valid());
// EXPECT_FALSE(lock.try_lock());
// EXPECT_TRUE(lock.lock());
// i += i;
// EXPECT_TRUE(lock.unlock());
// }};
// std::this_thread::sleep_for(std::chrono::seconds(1));
// EXPECT_EQ(i, 100);
// EXPECT_TRUE(lock.unlock());
// t2.join();
// EXPECT_EQ(i, 200);
}
#include "libipc/semaphore.h"
TEST(Sync, Semaphore) {
ipc::sync::semaphore sem;
EXPECT_TRUE(sem.open("test-sem"));
std::thread{[] {
ipc::sync::semaphore sem {"test-sem"};
EXPECT_TRUE(sem.post(1000));
}}.join();
for (int i = 0; i < 1000; ++i) {
EXPECT_TRUE(sem.wait(0));
}
EXPECT_FALSE(sem.wait(0));
}
#include "libipc/condition.h"
TEST(Sync, Condition) {
ipc::sync::condition cond;
EXPECT_TRUE(cond.open("test-cond"));
ipc::sync::mutex lock;
EXPECT_TRUE(lock.open("test-mutex"));
std::deque<int> que;
auto job = [&que](int num) {
ipc::sync::condition cond {"test-cond"};
ipc::sync::mutex lock {"test-mutex"};
for (int i = 0; i < 10; ++i) {
int val = 0;
{
std::lock_guard<ipc::sync::mutex> guard {lock};
while (que.empty()) {
ASSERT_TRUE(cond.wait(lock));
}
val = que.front();
que.pop_front();
}
EXPECT_NE(val, 0);
std::printf("test-cond-%d: %d\n", num, val);
}
for (;;) {
int val = 0;
{
std::lock_guard<ipc::sync::mutex> guard {lock};
while (que.empty()) {
ASSERT_TRUE(cond.wait(lock, 1000));
}
val = que.front();
que.pop_front();
}
if (val == 0) {
std::printf("test-cond-%d: exit.\n", num);
return;
}
std::printf("test-cond-%d: %d\n", num, val);
}
};
std::array<std::thread, 10> test_conds;
for (int i = 0; i < (int)test_conds.size(); ++i) {
test_conds[i] = std::thread{job, i};
}
for (int i = 1; i < 100; ++i) {
{
std::lock_guard<ipc::sync::mutex> guard {lock};
que.push_back(i);
ASSERT_TRUE(cond.notify(lock));
}
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
for (int i = 1; i < 100; ++i) {
{
std::lock_guard<ipc::sync::mutex> guard {lock};
que.push_back(i);
ASSERT_TRUE(cond.broadcast(lock));
}
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
{
std::lock_guard<ipc::sync::mutex> guard {lock};
for (int i = 0; i < (int)test_conds.size(); ++i) {
que.push_back(0);
}
ASSERT_TRUE(cond.broadcast(lock));
}
for (auto &t : test_conds) t.join();
}
/**
* https://stackoverflow.com/questions/51730660/is-this-a-bug-in-glibc-pthread
*/
TEST(Sync, ConditionRobust) {
printf("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 1\n");
ipc::sync::condition cond {"test-cond"};
printf("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 2\n");
ipc::sync::mutex lock {"test-mutex"};
printf("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 3\n");
ASSERT_TRUE(lock.lock());
std::thread unlock {[] {
printf("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWW 1\n");
ipc::sync::condition cond {"test-cond"};
printf("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWW 2\n");
ipc::sync::mutex lock {"test-mutex"};
printf("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWW 3\n");
{
std::lock_guard<ipc::sync::mutex> guard {lock};
}
std::this_thread::sleep_for(std::chrono::seconds(1));
printf("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWW 4\n");
ASSERT_TRUE(cond.broadcast(lock));
printf("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWW 5\n");
}};
printf("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 4\n");
ASSERT_TRUE(cond.wait(lock));
printf("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 5\n");
ASSERT_TRUE(lock.unlock());
printf("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 6\n");
unlock.join();
}

View File

@ -1,85 +0,0 @@
#include <thread>
#include <iostream>
#include "libipc/waiter.h"
#include "test.h"
TEST(Waiter, broadcast) {
for (int i = 0; i < 10; ++i) {
ipc::detail::waiter waiter;
std::thread ts[10];
int k = 0;
for (auto& t : ts) {
t = std::thread([&k] {
ipc::detail::waiter waiter {"test-ipc-waiter"};
EXPECT_TRUE(waiter.valid());
for (int i = 0; i < 9; ++i) {
while (!waiter.wait_if([&k, &i] { return k == i; })) ;
}
});
}
EXPECT_TRUE(waiter.open("test-ipc-waiter"));
std::cout << "waiting for broadcast...\n";
for (k = 1; k < 10; ++k) {
std::cout << "broadcast: " << k << "\n";
ASSERT_TRUE(waiter.broadcast());
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
for (auto& t : ts) t.join();
std::cout << "quit... " << i << "\n";
}
}
TEST(Waiter, quit_waiting) {
ipc::detail::waiter waiter;
EXPECT_TRUE(waiter.open("test-ipc-waiter"));
std::thread t1 {
[&waiter] {
EXPECT_TRUE(waiter.wait_if([] { return true; }));
}
};
bool quit = false;
std::thread t2 {
[&quit] {
ipc::detail::waiter waiter {"test-ipc-waiter"};
EXPECT_TRUE(waiter.wait_if([&quit] { return !quit; }));
}
};
std::this_thread::sleep_for(std::chrono::milliseconds(100));
EXPECT_TRUE(waiter.quit_waiting());
t1.join();
ASSERT_TRUE(t2.joinable());
EXPECT_TRUE(waiter.open("test-ipc-waiter"));
std::cout << "nofify quit...\n";
quit = true;
EXPECT_TRUE(waiter.notify());
t2.join();
std::cout << "quit... \n";
}
TEST(Waiter, clear) {
{
ipc::detail::waiter w{"my-waiter"};
ASSERT_TRUE(w.valid());
EXPECT_TRUE(ipc_ut::expect_exist("my-waiter_WAITER_COND_", true));
EXPECT_TRUE(ipc_ut::expect_exist("my-waiter_WAITER_LOCK_", true));
w.clear();
ASSERT_TRUE(!w.valid());
EXPECT_TRUE(ipc_ut::expect_exist("my-waiter_WAITER_COND_", false));
EXPECT_TRUE(ipc_ut::expect_exist("my-waiter_WAITER_LOCK_", false));
}
{
ipc::detail::waiter w{"my-waiter"};
EXPECT_TRUE(ipc_ut::expect_exist("my-waiter_WAITER_COND_", true));
EXPECT_TRUE(ipc_ut::expect_exist("my-waiter_WAITER_LOCK_", true));
ipc::detail::waiter::clear_storage("my-waiter");
EXPECT_TRUE(ipc_ut::expect_exist("my-waiter_WAITER_COND_", false));
EXPECT_TRUE(ipc_ut::expect_exist("my-waiter_WAITER_LOCK_", false));
}
}

View File

@ -14,11 +14,6 @@
#include "thread_pool.h"
#include "libipc/platform/detail.h"
#ifdef IPC_OS_LINUX_
#include <fcntl.h> // ::open
#endif
namespace ipc_ut {
template <typename Dur>
@ -88,23 +83,4 @@ inline static thread_pool & reader() {
return pool;
}
#ifdef IPC_OS_LINUX_
inline bool check_exist(char const *name) noexcept {
int fd = ::open((std::string{"/dev/shm/"} + name).c_str(), O_RDONLY);
if (fd == -1) {
return false;
}
::close(fd);
return true;
}
#endif
inline bool expect_exist(char const *name, bool expected) noexcept {
#ifdef IPC_OS_LINUX_
return ipc_ut::check_exist(name) == expected;
#else
return true;
#endif
}
} // namespace ipc_ut

View File

@ -1,384 +0,0 @@
/**
* @file test_buffer.cpp
* @brief Comprehensive unit tests for ipc::buffer class
*
* This test suite covers all public interfaces of the buffer class including:
* - Constructors (default, with pointer and destructor, from array, from char)
* - Move semantics
* - Copy operations through assignment
* - Basic operations (empty, data, size)
* - Conversion methods (to_tuple, to_vector, get<T>)
* - Comparison operators
*/
#include <gtest/gtest.h>
#include <cstring>
#include <vector>
#include "libipc/buffer.h"
using namespace ipc;
namespace {
// Custom destructor tracker for testing
struct DestructorTracker {
static int count;
static void reset() { count = 0; }
static void destructor(void* p, std::size_t) {
++count;
delete[] static_cast<char*>(p);
}
};
int DestructorTracker::count = 0;
} // anonymous namespace
class BufferTest : public ::testing::Test {
protected:
void SetUp() override {
DestructorTracker::reset();
}
};
// Test default constructor
TEST_F(BufferTest, DefaultConstructor) {
buffer buf;
EXPECT_TRUE(buf.empty());
EXPECT_EQ(buf.size(), 0u);
EXPECT_EQ(buf.data(), nullptr);
}
// Test constructor with pointer, size, and destructor
TEST_F(BufferTest, ConstructorWithDestructor) {
const char* test_data = "Hello, World!";
std::size_t size = std::strlen(test_data) + 1;
char* data = new char[size];
std::strcpy(data, test_data);
buffer buf(data, size, DestructorTracker::destructor);
EXPECT_FALSE(buf.empty());
EXPECT_EQ(buf.size(), size);
EXPECT_NE(buf.data(), nullptr);
EXPECT_STREQ(static_cast<const char*>(buf.data()), test_data);
}
// Test destructor is called
TEST_F(BufferTest, DestructorCalled) {
{
char* data = new char[100];
buffer buf(data, 100, DestructorTracker::destructor);
EXPECT_EQ(DestructorTracker::count, 0);
}
EXPECT_EQ(DestructorTracker::count, 1);
}
// Test constructor with mem_to_free parameter
// Scenario: allocate a large block, but only use a portion as data
TEST_F(BufferTest, ConstructorWithMemToFree) {
// Allocate a block of 100 bytes
char* allocated_block = new char[100];
// But only use the middle 50 bytes as data (offset 25)
char* data_start = allocated_block + 25;
std::strcpy(data_start, "Offset data");
// When destroyed, should free the entire allocated_block, not just data_start
buffer buf(data_start, 50, DestructorTracker::destructor, allocated_block);
EXPECT_FALSE(buf.empty());
EXPECT_EQ(buf.size(), 50u);
EXPECT_EQ(buf.data(), data_start);
EXPECT_STREQ(static_cast<const char*>(buf.data()), "Offset data");
// Destructor will be called with allocated_block (not data_start)
// This correctly frees the entire allocation
}
// Test constructor without destructor
TEST_F(BufferTest, ConstructorWithoutDestructor) {
char stack_data[20] = "Stack data";
buffer buf(stack_data, 20);
EXPECT_FALSE(buf.empty());
EXPECT_EQ(buf.size(), 20u);
EXPECT_EQ(buf.data(), stack_data);
}
// Test constructor from byte array
TEST_F(BufferTest, ConstructorFromByteArray) {
byte_t data[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
buffer buf(data);
EXPECT_FALSE(buf.empty());
EXPECT_EQ(buf.size(), 10u);
const byte_t* buf_data = buf.get<const byte_t*>();
for (int i = 0; i < 10; ++i) {
EXPECT_EQ(buf_data[i], i);
}
}
// Test constructor from single char
TEST_F(BufferTest, ConstructorFromChar) {
char c = 'X';
buffer buf(c);
EXPECT_FALSE(buf.empty());
EXPECT_EQ(buf.size(), sizeof(char));
EXPECT_EQ(*buf.get<const char*>(), 'X');
}
// Test move constructor
TEST_F(BufferTest, MoveConstructor) {
char* data = new char[30];
std::strcpy(data, "Move test");
buffer buf1(data, 30, DestructorTracker::destructor);
void* original_ptr = buf1.data();
std::size_t original_size = buf1.size();
buffer buf2(std::move(buf1));
// buf2 should have the original data
EXPECT_EQ(buf2.data(), original_ptr);
EXPECT_EQ(buf2.size(), original_size);
EXPECT_FALSE(buf2.empty());
// buf1 should be empty after move
EXPECT_TRUE(buf1.empty());
EXPECT_EQ(buf1.size(), 0u);
}
// Test swap
TEST_F(BufferTest, Swap) {
char* data1 = new char[20];
char* data2 = new char[30];
std::strcpy(data1, "Buffer 1");
std::strcpy(data2, "Buffer 2");
buffer buf1(data1, 20, DestructorTracker::destructor);
buffer buf2(data2, 30, DestructorTracker::destructor);
void* ptr1 = buf1.data();
void* ptr2 = buf2.data();
std::size_t size1 = buf1.size();
std::size_t size2 = buf2.size();
buf1.swap(buf2);
EXPECT_EQ(buf1.data(), ptr2);
EXPECT_EQ(buf1.size(), size2);
EXPECT_EQ(buf2.data(), ptr1);
EXPECT_EQ(buf2.size(), size1);
}
// Test assignment operator (move semantics)
TEST_F(BufferTest, AssignmentOperator) {
char* data = new char[40];
std::strcpy(data, "Assignment test");
buffer buf1(data, 40, DestructorTracker::destructor);
void* original_ptr = buf1.data();
buffer buf2;
buf2 = std::move(buf1);
EXPECT_EQ(buf2.data(), original_ptr);
EXPECT_FALSE(buf2.empty());
}
// Test empty() method
TEST_F(BufferTest, EmptyMethod) {
buffer buf1;
EXPECT_TRUE(buf1.empty());
char* data = new char[10];
buffer buf2(data, 10, DestructorTracker::destructor);
EXPECT_FALSE(buf2.empty());
}
// Test data() const method
TEST_F(BufferTest, DataConstMethod) {
const char* test_str = "Const data test";
std::size_t size = std::strlen(test_str) + 1;
char* data = new char[size];
std::strcpy(data, test_str);
const buffer buf(data, size, DestructorTracker::destructor);
const void* const_data = buf.data();
EXPECT_NE(const_data, nullptr);
EXPECT_STREQ(static_cast<const char*>(const_data), test_str);
}
// Test get<T>() template method
TEST_F(BufferTest, GetTemplateMethod) {
int* int_data = new int[5]{1, 2, 3, 4, 5};
buffer buf(int_data, 5 * sizeof(int), [](void* p, std::size_t) {
delete[] static_cast<int*>(p);
});
int* retrieved = buf.get<int*>();
EXPECT_NE(retrieved, nullptr);
EXPECT_EQ(retrieved[0], 1);
EXPECT_EQ(retrieved[4], 5);
}
// Test to_tuple() non-const version
TEST_F(BufferTest, ToTupleNonConst) {
char* data = new char[25];
std::strcpy(data, "Tuple test");
buffer buf(data, 25, DestructorTracker::destructor);
// C++14 compatible: use std::get instead of structured binding
auto tuple = buf.to_tuple();
auto ptr = std::get<0>(tuple);
auto size = std::get<1>(tuple);
EXPECT_EQ(ptr, buf.data());
EXPECT_EQ(size, buf.size());
EXPECT_EQ(size, 25u);
}
// Test to_tuple() const version
TEST_F(BufferTest, ToTupleConst) {
char* data = new char[30];
std::strcpy(data, "Const tuple");
const buffer buf(data, 30, DestructorTracker::destructor);
// C++14 compatible: use std::get instead of structured binding
auto tuple = buf.to_tuple();
auto ptr = std::get<0>(tuple);
auto size = std::get<1>(tuple);
EXPECT_EQ(ptr, buf.data());
EXPECT_EQ(size, buf.size());
EXPECT_EQ(size, 30u);
}
// Test to_vector() method
TEST_F(BufferTest, ToVector) {
byte_t data_arr[5] = {10, 20, 30, 40, 50};
buffer buf(data_arr, 5);
std::vector<byte_t> vec = buf.to_vector();
ASSERT_EQ(vec.size(), 5u);
EXPECT_EQ(vec[0], 10);
EXPECT_EQ(vec[1], 20);
EXPECT_EQ(vec[2], 30);
EXPECT_EQ(vec[3], 40);
EXPECT_EQ(vec[4], 50);
}
// Test equality operator
TEST_F(BufferTest, EqualityOperator) {
byte_t data1[5] = {1, 2, 3, 4, 5};
byte_t data2[5] = {1, 2, 3, 4, 5};
byte_t data3[5] = {5, 4, 3, 2, 1};
buffer buf1(data1, 5);
buffer buf2(data2, 5);
buffer buf3(data3, 5);
EXPECT_TRUE(buf1 == buf2);
EXPECT_FALSE(buf1 == buf3);
}
// Test inequality operator
TEST_F(BufferTest, InequalityOperator) {
byte_t data1[5] = {1, 2, 3, 4, 5};
byte_t data2[5] = {1, 2, 3, 4, 5};
byte_t data3[5] = {5, 4, 3, 2, 1};
buffer buf1(data1, 5);
buffer buf2(data2, 5);
buffer buf3(data3, 5);
EXPECT_FALSE(buf1 != buf2);
EXPECT_TRUE(buf1 != buf3);
}
// Test size mismatch in equality
TEST_F(BufferTest, EqualityWithDifferentSizes) {
byte_t data1[5] = {1, 2, 3, 4, 5};
byte_t data2[3] = {1, 2, 3};
buffer buf1(data1, 5);
buffer buf2(data2, 3);
EXPECT_FALSE(buf1 == buf2);
EXPECT_TRUE(buf1 != buf2);
}
// Test empty buffers comparison
TEST_F(BufferTest, EmptyBuffersComparison) {
buffer buf1;
buffer buf2;
EXPECT_TRUE(buf1 == buf2);
EXPECT_FALSE(buf1 != buf2);
}
// Test large buffer
TEST_F(BufferTest, LargeBuffer) {
const std::size_t large_size = 1024 * 1024; // 1MB
char* large_data = new char[large_size];
// Fill with pattern
for (std::size_t i = 0; i < large_size; ++i) {
large_data[i] = static_cast<char>(i % 256);
}
buffer buf(large_data, large_size, [](void* p, std::size_t) {
delete[] static_cast<char*>(p);
});
EXPECT_FALSE(buf.empty());
EXPECT_EQ(buf.size(), large_size);
// Verify pattern
const char* data_ptr = buf.get<const char*>();
for (std::size_t i = 0; i < 100; ++i) { // Check first 100 bytes
EXPECT_EQ(data_ptr[i], static_cast<char>(i % 256));
}
}
// Test multiple move operations
TEST_F(BufferTest, MultipleMoves) {
char* data = new char[15];
std::strcpy(data, "Multi-move");
void* original_ptr = data;
buffer buf1(data, 15, DestructorTracker::destructor);
buffer buf2(std::move(buf1));
buffer buf3(std::move(buf2));
buffer buf4(std::move(buf3));
EXPECT_EQ(buf4.data(), original_ptr);
EXPECT_TRUE(buf1.empty());
EXPECT_TRUE(buf2.empty());
EXPECT_TRUE(buf3.empty());
EXPECT_FALSE(buf4.empty());
}
// Test self-assignment safety
TEST_F(BufferTest, SelfAssignment) {
char* data = new char[20];
std::strcpy(data, "Self-assign");
buffer buf(data, 20, DestructorTracker::destructor);
void* original_ptr = buf.data();
std::size_t original_size = buf.size();
buf = std::move(buf); // Self-assignment
// Should remain valid
EXPECT_EQ(buf.data(), original_ptr);
EXPECT_EQ(buf.size(), original_size);
}

View File

@ -1,550 +0,0 @@
/**
* @file test_condition.cpp
* @brief Comprehensive unit tests for ipc::sync::condition class
*
* This test suite covers:
* - Condition variable construction (default and named)
* - Wait, notify, and broadcast operations
* - Timed wait with timeout
* - Integration with mutex
* - Producer-consumer patterns with condition variables
* - Resource cleanup
*/
#include <gtest/gtest.h>
#include <thread>
#include <chrono>
#include <atomic>
#include <vector>
#include "libipc/condition.h"
#include "libipc/mutex.h"
#include "libipc/def.h"
using namespace ipc;
using namespace ipc::sync;
namespace {
std::string generate_unique_cv_name(const char* prefix) {
static int counter = 0;
return std::string(prefix) + "_cv_" + std::to_string(++counter);
}
} // anonymous namespace
class ConditionTest : public ::testing::Test {
protected:
void TearDown() override {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
};
// Test default constructor
TEST_F(ConditionTest, DefaultConstructor) {
condition cv;
}
// Test named constructor
TEST_F(ConditionTest, NamedConstructor) {
std::string name = generate_unique_cv_name("named");
condition cv(name.c_str());
EXPECT_TRUE(cv.valid());
}
// Test native() methods
TEST_F(ConditionTest, NativeHandle) {
std::string name = generate_unique_cv_name("native");
condition cv(name.c_str());
ASSERT_TRUE(cv.valid());
const void* const_handle = static_cast<const condition&>(cv).native();
void* handle = cv.native();
EXPECT_NE(const_handle, nullptr);
EXPECT_NE(handle, nullptr);
}
// Test valid() method
TEST_F(ConditionTest, Valid) {
condition cv1;
std::string name = generate_unique_cv_name("valid");
condition cv2(name.c_str());
EXPECT_TRUE(cv2.valid());
}
// Test open() method
TEST_F(ConditionTest, Open) {
std::string name = generate_unique_cv_name("open");
condition cv;
bool result = cv.open(name.c_str());
EXPECT_TRUE(result);
EXPECT_TRUE(cv.valid());
}
// Test close() method
TEST_F(ConditionTest, Close) {
std::string name = generate_unique_cv_name("close");
condition cv(name.c_str());
ASSERT_TRUE(cv.valid());
cv.close();
EXPECT_FALSE(cv.valid());
}
// Test clear() method
TEST_F(ConditionTest, Clear) {
std::string name = generate_unique_cv_name("clear");
condition cv(name.c_str());
ASSERT_TRUE(cv.valid());
cv.clear();
EXPECT_FALSE(cv.valid());
}
// Test clear_storage() static method
TEST_F(ConditionTest, ClearStorage) {
std::string name = generate_unique_cv_name("clear_storage");
{
condition cv(name.c_str());
EXPECT_TRUE(cv.valid());
}
condition::clear_storage(name.c_str());
}
// Test basic wait and notify
TEST_F(ConditionTest, WaitNotify) {
std::string cv_name = generate_unique_cv_name("wait_notify");
std::string mtx_name = generate_unique_cv_name("wait_notify_mtx");
condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid());
std::atomic<bool> notified{false};
std::thread waiter([&]() {
mtx.lock();
cv.wait(mtx);
notified.store(true);
mtx.unlock();
});
std::this_thread::sleep_for(std::chrono::milliseconds(50));
mtx.lock();
cv.notify(mtx);
mtx.unlock();
waiter.join();
EXPECT_TRUE(notified.load());
}
// Test broadcast to multiple waiters
TEST_F(ConditionTest, Broadcast) {
std::string cv_name = generate_unique_cv_name("broadcast");
std::string mtx_name = generate_unique_cv_name("broadcast_mtx");
condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid());
std::atomic<int> notified_count{0};
const int num_waiters = 5;
std::vector<std::thread> waiters;
for (int i = 0; i < num_waiters; ++i) {
waiters.emplace_back([&]() {
mtx.lock();
cv.wait(mtx);
++notified_count;
mtx.unlock();
});
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
mtx.lock();
cv.broadcast(mtx);
mtx.unlock();
for (auto& t : waiters) {
t.join();
}
EXPECT_EQ(notified_count.load(), num_waiters);
}
// Test timed wait with timeout
TEST_F(ConditionTest, TimedWait) {
std::string cv_name = generate_unique_cv_name("timed_wait");
std::string mtx_name = generate_unique_cv_name("timed_wait_mtx");
condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid());
auto start = std::chrono::steady_clock::now();
mtx.lock();
bool result = cv.wait(mtx, 100); // 100ms timeout
mtx.unlock();
auto end = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
EXPECT_FALSE(result); // Should timeout
EXPECT_GE(elapsed, 80); // Allow some tolerance
}
// Test wait with immediate notify
TEST_F(ConditionTest, ImmediateNotify) {
std::string cv_name = generate_unique_cv_name("immediate");
std::string mtx_name = generate_unique_cv_name("immediate_mtx");
condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid());
std::atomic<bool> wait_started{false};
std::atomic<bool> notified{false};
std::thread waiter([&]() {
mtx.lock();
wait_started.store(true);
cv.wait(mtx, 1000); // 1 second timeout
notified.store(true);
mtx.unlock();
});
// Wait for waiter to start
while (!wait_started.load()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
mtx.lock();
cv.notify(mtx);
mtx.unlock();
waiter.join();
EXPECT_TRUE(notified.load());
}
// Test producer-consumer with condition variable
TEST_F(ConditionTest, ProducerConsumer) {
std::string cv_name = generate_unique_cv_name("prod_cons");
std::string mtx_name = generate_unique_cv_name("prod_cons_mtx");
condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid());
std::atomic<int> buffer{0};
std::atomic<bool> ready{false};
std::atomic<int> consumed_value{0};
std::thread producer([&]() {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
mtx.lock();
buffer.store(42);
ready.store(true);
cv.notify(mtx);
mtx.unlock();
});
std::thread consumer([&]() {
mtx.lock();
while (!ready.load()) {
cv.wait(mtx, 2000);
}
consumed_value.store(buffer.load());
mtx.unlock();
});
producer.join();
consumer.join();
EXPECT_EQ(consumed_value.load(), 42);
}
// Test multiple notify operations
TEST_F(ConditionTest, MultipleNotify) {
std::string cv_name = generate_unique_cv_name("multi_notify");
std::string mtx_name = generate_unique_cv_name("multi_notify_mtx");
condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid());
std::atomic<int> notify_count{0};
const int num_notifications = 3;
std::thread waiter([&]() {
for (int i = 0; i < num_notifications; ++i) {
mtx.lock();
cv.wait(mtx, 1000);
++notify_count;
mtx.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
});
for (int i = 0; i < num_notifications; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
mtx.lock();
cv.notify(mtx);
mtx.unlock();
}
waiter.join();
EXPECT_EQ(notify_count.load(), num_notifications);
}
// Test notify vs broadcast
TEST_F(ConditionTest, NotifyVsBroadcast) {
std::string cv_name = generate_unique_cv_name("notify_vs_broadcast");
std::string mtx_name = generate_unique_cv_name("notify_vs_broadcast_mtx");
condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid());
// Test notify (should wake one)
std::atomic<int> notify_woken{0};
std::vector<std::thread> notify_waiters;
for (int i = 0; i < 3; ++i) {
notify_waiters.emplace_back([&]() {
mtx.lock();
cv.wait(mtx, 100);
++notify_woken;
mtx.unlock();
});
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
mtx.lock();
cv.notify(mtx); // Wake one
mtx.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(150));
for (auto& t : notify_waiters) {
t.join();
}
// At least one should be woken by notify
EXPECT_GE(notify_woken.load(), 1);
}
// Test condition variable with spurious wakeups pattern
TEST_F(ConditionTest, SpuriousWakeupPattern) {
std::string cv_name = generate_unique_cv_name("spurious");
std::string mtx_name = generate_unique_cv_name("spurious_mtx");
condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid());
std::atomic<bool> predicate{false};
std::atomic<bool> done{false};
std::thread waiter([&]() {
mtx.lock();
while (!predicate.load()) {
if (!cv.wait(mtx, 100)) {
// Timeout - check predicate again
if (predicate.load()) break;
}
}
done.store(true);
mtx.unlock();
});
std::this_thread::sleep_for(std::chrono::milliseconds(50));
mtx.lock();
predicate.store(true);
cv.notify(mtx);
mtx.unlock();
waiter.join();
EXPECT_TRUE(done.load());
}
// Test reopen after close
TEST_F(ConditionTest, ReopenAfterClose) {
std::string name = generate_unique_cv_name("reopen");
condition cv;
ASSERT_TRUE(cv.open(name.c_str()));
EXPECT_TRUE(cv.valid());
cv.close();
EXPECT_FALSE(cv.valid());
ASSERT_TRUE(cv.open(name.c_str()));
EXPECT_TRUE(cv.valid());
}
// Test named condition variable sharing between threads
TEST_F(ConditionTest, NamedSharing) {
std::string cv_name = generate_unique_cv_name("sharing");
std::string mtx_name = generate_unique_cv_name("sharing_mtx");
std::atomic<int> value{0};
std::thread t1([&]() {
condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid());
mtx.lock();
cv.wait(mtx, 1000);
value.store(100);
mtx.unlock();
});
std::thread t2([&]() {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid());
mtx.lock();
cv.notify(mtx);
mtx.unlock();
});
t1.join();
t2.join();
EXPECT_EQ(value.load(), 100);
}
// Test infinite wait
TEST_F(ConditionTest, InfiniteWait) {
std::string cv_name = generate_unique_cv_name("infinite");
std::string mtx_name = generate_unique_cv_name("infinite_mtx");
condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid());
std::atomic<bool> woken{false};
std::thread waiter([&]() {
mtx.lock();
cv.wait(mtx, invalid_value); // Infinite wait
woken.store(true);
mtx.unlock();
});
std::this_thread::sleep_for(std::chrono::milliseconds(100));
mtx.lock();
cv.notify(mtx);
mtx.unlock();
waiter.join();
EXPECT_TRUE(woken.load());
}
// Test broadcast with sequential waiters
TEST_F(ConditionTest, BroadcastSequential) {
std::string cv_name = generate_unique_cv_name("broadcast_seq");
std::string mtx_name = generate_unique_cv_name("broadcast_seq_mtx");
condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid());
ASSERT_TRUE(mtx.valid());
std::atomic<int> processed{0};
const int num_threads = 4;
std::vector<std::thread> threads;
for (int i = 0; i < num_threads; ++i) {
threads.emplace_back([&]() {
mtx.lock();
cv.wait(mtx, 2000);
++processed;
mtx.unlock();
});
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
mtx.lock();
cv.broadcast(mtx);
mtx.unlock();
for (auto& t : threads) {
t.join();
}
EXPECT_EQ(processed.load(), num_threads);
}
// Test operations after clear
TEST_F(ConditionTest, AfterClear) {
std::string cv_name = generate_unique_cv_name("after_clear");
std::string mtx_name = generate_unique_cv_name("after_clear_mtx");
condition cv(cv_name.c_str());
mutex mtx(mtx_name.c_str());
ASSERT_TRUE(cv.valid());
cv.clear();
EXPECT_FALSE(cv.valid());
// Operations after clear should fail gracefully
mtx.lock();
EXPECT_FALSE(cv.wait(mtx, 10));
EXPECT_FALSE(cv.notify(mtx));
EXPECT_FALSE(cv.broadcast(mtx));
mtx.unlock();
}

View File

@ -20,7 +20,6 @@ namespace {
constexpr int LoopCount = 10000;
constexpr int MultiMax = 8;
constexpr int TestBuffMax = 65536;
struct msg_head {
int id_;
@ -29,7 +28,7 @@ struct msg_head {
class rand_buf : public buffer {
public:
rand_buf() {
int size = capo::random<>{(int)sizeof(msg_head), TestBuffMax}();
int size = capo::random<>{sizeof(msg_head), 65536}();
*this = buffer(new char[size], size, [](void * p, std::size_t) {
delete [] static_cast<char *>(p);
});
@ -110,10 +109,10 @@ void test_sr(char const * name, int s_cnt, int r_cnt) {
for (int k = 0; k < s_cnt; ++k) {
ipc_ut::sender() << [name, &sw, r_cnt, k] {
Que que { name, ipc::sender };
ASSERT_TRUE(que.wait_for_recv(r_cnt));
EXPECT_TRUE(que.wait_for_recv(r_cnt));
sw.start();
for (int i = 0; i < (int)data_set__.get().size(); ++i) {
ASSERT_TRUE(que.send(data_set__.get()[i]));
EXPECT_TRUE(que.send(data_set__.get()[i]));
}
};
}
@ -133,7 +132,7 @@ void test_sr(char const * name, int s_cnt, int r_cnt) {
if (data_set != got) {
printf("data_set__.get()[%d] != got, size = %zd/%zd\n",
i, data_set.size(), got.size());
ASSERT_TRUE(false);
EXPECT_TRUE(false);
}
}
};
@ -141,7 +140,7 @@ void test_sr(char const * name, int s_cnt, int r_cnt) {
ipc_ut::sender().wait_for_done();
Que que { name };
ASSERT_TRUE(que.wait_for_recv(r_cnt));
EXPECT_TRUE(que.wait_for_recv(r_cnt));
for (int k = 0; k < r_cnt; ++k) {
que.send(rand_buf{msg_head{-1}});
}
@ -151,66 +150,18 @@ void test_sr(char const * name, int s_cnt, int r_cnt) {
} // internal-linkage
TEST(IPC, clear) {
{
chan<relat::single, relat::single, trans::unicast> c{"ssu"};
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__AC_CONN__ssu", true));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__CC_CONN__ssu_WAITER_COND_", true));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__CC_CONN__ssu_WAITER_LOCK_", true));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__RD_CONN__ssu_WAITER_COND_", true));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__RD_CONN__ssu_WAITER_LOCK_", true));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__WT_CONN__ssu_WAITER_COND_", true));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__WT_CONN__ssu_WAITER_LOCK_", true));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__QU_CONN__ssu__64__16", true));
c.clear();
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__AC_CONN__ssu", false));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__CC_CONN__ssu_WAITER_COND_", false));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__CC_CONN__ssu_WAITER_LOCK_", false));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__RD_CONN__ssu_WAITER_COND_", false));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__RD_CONN__ssu_WAITER_LOCK_", false));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__WT_CONN__ssu_WAITER_COND_", false));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__WT_CONN__ssu_WAITER_LOCK_", false));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__QU_CONN__ssu__64__16", false));
}
{
chan<relat::single, relat::single, trans::unicast> c{"ssu"};
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__AC_CONN__ssu", true));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__CC_CONN__ssu_WAITER_COND_", true));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__CC_CONN__ssu_WAITER_LOCK_", true));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__RD_CONN__ssu_WAITER_COND_", true));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__RD_CONN__ssu_WAITER_LOCK_", true));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__WT_CONN__ssu_WAITER_COND_", true));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__WT_CONN__ssu_WAITER_LOCK_", true));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__QU_CONN__ssu__64__16", true));
chan<relat::single, relat::single, trans::unicast>::clear_storage("ssu");
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__AC_CONN__ssu", false));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__CC_CONN__ssu_WAITER_COND_", false));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__CC_CONN__ssu_WAITER_LOCK_", false));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__RD_CONN__ssu_WAITER_COND_", false));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__RD_CONN__ssu_WAITER_LOCK_", false));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__WT_CONN__ssu_WAITER_COND_", false));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__WT_CONN__ssu_WAITER_LOCK_", false));
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__QU_CONN__ssu__64__16", false));
c.release(); // Call this interface to prevent destruction-time exceptions.
}
}
TEST(IPC, basic_ssu) {
TEST(IPC, basic) {
test_basic<relat::single, relat::single, trans::unicast >("ssu");
}
TEST(IPC, basic_smb) {
test_basic<relat::single, relat::multi , trans::unicast >("smu");
test_basic<relat::multi , relat::multi , trans::unicast >("mmu");
test_basic<relat::single, relat::multi , trans::broadcast>("smb");
}
TEST(IPC, basic_mmb) {
test_basic<relat::multi , relat::multi , trans::broadcast>("mmb");
}
TEST(IPC, 1v1) {
test_sr<relat::single, relat::single, trans::unicast >("ssu", 1, 1);
//test_sr<relat::single, relat::multi , trans::unicast >("smu", 1, 1);
//test_sr<relat::multi , relat::multi , trans::unicast >("mmu", 1, 1);
test_sr<relat::single, relat::multi , trans::unicast >("smu", 1, 1);
test_sr<relat::multi , relat::multi , trans::unicast >("mmu", 1, 1);
test_sr<relat::single, relat::multi , trans::broadcast>("smb", 1, 1);
test_sr<relat::multi , relat::multi , trans::broadcast>("mmb", 1, 1);
}

View File

@ -1,643 +0,0 @@
/**
* @file test_ipc_channel.cpp
* @brief Comprehensive unit tests for ipc::route and ipc::channel classes
*
* This test suite covers:
* - Route (single producer, multiple consumer) functionality
* - Channel (multiple producer, multiple consumer) functionality
* - Construction, connection, and disconnection
* - Send and receive operations (blocking and non-blocking)
* - Timeout handling
* - Named channels with prefix
* - Resource cleanup and storage management
* - Clone operations
* - Wait for receiver functionality
* - Error conditions
*/
#include <gtest/gtest.h>
#include <thread>
#include <chrono>
#include <atomic>
#include <vector>
#include <string>
#include <cstring>
#include <mutex>
#include <condition_variable>
#include "libipc/ipc.h"
#include "libipc/buffer.h"
using namespace ipc;
namespace {
// Simple latch implementation for C++14 (similar to C++20 std::latch)
class latch {
public:
explicit latch(std::ptrdiff_t count) : count_(count) {}
void count_down() {
std::unique_lock<std::mutex> lock(mutex_);
if (--count_ <= 0) {
cv_.notify_all();
}
}
void wait() {
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, [this] { return count_ <= 0; });
}
private:
std::ptrdiff_t count_;
std::mutex mutex_;
std::condition_variable cv_;
};
std::string generate_unique_ipc_name(const char* prefix) {
static int counter = 0;
return std::string(prefix) + "_ipc_" + std::to_string(++counter);
}
// Helper to create a test buffer with data
buffer make_test_buffer(const std::string& data) {
char* mem = new char[data.size() + 1];
std::strcpy(mem, data.c_str());
return buffer(mem, data.size() + 1, [](void* p, std::size_t) {
delete[] static_cast<char*>(p);
});
}
// Helper to check buffer content
bool check_buffer_content(const buffer& buf, const std::string& expected) {
if (buf.empty() || buf.size() != expected.size() + 1) {
return false;
}
return std::strcmp(static_cast<const char*>(buf.data()), expected.c_str()) == 0;
}
} // anonymous namespace
// ========== Route Tests (Single Producer, Multiple Consumer) ==========
class RouteTest : public ::testing::Test {
protected:
void TearDown() override {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
};
// Test default construction
TEST_F(RouteTest, DefaultConstruction) {
route r;
EXPECT_FALSE(r.valid());
}
// Test construction with name
TEST_F(RouteTest, ConstructionWithName) {
std::string name = generate_unique_ipc_name("route_ctor");
route r(name.c_str(), sender);
EXPECT_TRUE(r.valid());
EXPECT_STREQ(r.name(), name.c_str());
}
// Test construction with prefix
TEST_F(RouteTest, ConstructionWithPrefix) {
std::string name = generate_unique_ipc_name("route_prefix");
route r(prefix{"my_prefix"}, name.c_str(), sender);
EXPECT_TRUE(r.valid());
}
// Test move constructor
TEST_F(RouteTest, MoveConstructor) {
std::string name = generate_unique_ipc_name("route_move");
route r1(name.c_str(), sender);
ASSERT_TRUE(r1.valid());
const char* name_ptr = r1.name();
route r2(std::move(r1));
EXPECT_TRUE(r2.valid());
EXPECT_STREQ(r2.name(), name_ptr);
}
// Test assignment operator
TEST_F(RouteTest, Assignment) {
std::string name = generate_unique_ipc_name("route_assign");
route r1(name.c_str(), sender);
route r2;
r2 = std::move(r1);
EXPECT_TRUE(r2.valid());
}
// Test connect method
TEST_F(RouteTest, Connect) {
std::string name = generate_unique_ipc_name("route_connect");
route r;
bool connected = r.connect(name.c_str(), sender);
EXPECT_TRUE(connected);
EXPECT_TRUE(r.valid());
}
// Test connect with prefix
TEST_F(RouteTest, ConnectWithPrefix) {
std::string name = generate_unique_ipc_name("route_connect_prefix");
route r;
bool connected = r.connect(prefix{"test"}, name.c_str(), sender);
EXPECT_TRUE(connected);
EXPECT_TRUE(r.valid());
}
// Test reconnect
TEST_F(RouteTest, Reconnect) {
std::string name = generate_unique_ipc_name("route_reconnect");
route r(name.c_str(), sender);
ASSERT_TRUE(r.valid());
bool reconnected = r.reconnect(sender | receiver);
EXPECT_TRUE(reconnected);
}
// Test disconnect
TEST_F(RouteTest, Disconnect) {
std::string name = generate_unique_ipc_name("route_disconnect");
route r(name.c_str(), sender);
ASSERT_TRUE(r.valid());
r.disconnect();
// After disconnect, behavior depends on implementation
}
// Test clone
TEST_F(RouteTest, Clone) {
std::string name = generate_unique_ipc_name("route_clone");
route r1(name.c_str(), sender);
ASSERT_TRUE(r1.valid());
route r2 = r1.clone();
EXPECT_TRUE(r2.valid());
EXPECT_STREQ(r1.name(), r2.name());
}
// Test mode accessor
TEST_F(RouteTest, Mode) {
std::string name = generate_unique_ipc_name("route_mode");
route r(name.c_str(), sender);
EXPECT_EQ(r.mode(), sender);
}
// Test release
TEST_F(RouteTest, Release) {
std::string name = generate_unique_ipc_name("route_release");
route r(name.c_str(), sender);
ASSERT_TRUE(r.valid());
r.release();
EXPECT_FALSE(r.valid());
}
// Test clear
TEST_F(RouteTest, Clear) {
std::string name = generate_unique_ipc_name("route_clear");
route r(name.c_str(), sender);
ASSERT_TRUE(r.valid());
r.clear();
EXPECT_FALSE(r.valid());
}
// Test clear_storage static method
TEST_F(RouteTest, ClearStorage) {
std::string name = generate_unique_ipc_name("route_clear_storage");
{
route r(name.c_str(), sender);
EXPECT_TRUE(r.valid());
}
route::clear_storage(name.c_str());
}
// Test clear_storage with prefix
TEST_F(RouteTest, ClearStorageWithPrefix) {
std::string name = generate_unique_ipc_name("route_clear_prefix");
{
route r(prefix{"test"}, name.c_str(), sender);
EXPECT_TRUE(r.valid());
}
route::clear_storage(prefix{"test"}, name.c_str());
}
// Test send without receiver (should fail)
TEST_F(RouteTest, SendWithoutReceiver) {
std::string name = generate_unique_ipc_name("route_send_no_recv");
route r(name.c_str(), sender);
ASSERT_TRUE(r.valid());
buffer buf = make_test_buffer("test");
bool sent = r.send(buf, 10); // 10ms timeout
EXPECT_FALSE(sent); // Should fail - no receiver
}
// Test try_send without receiver
TEST_F(RouteTest, TrySendWithoutReceiver) {
std::string name = generate_unique_ipc_name("route_try_send_no_recv");
route r(name.c_str(), sender);
ASSERT_TRUE(r.valid());
buffer buf = make_test_buffer("test");
bool sent = r.try_send(buf, 10);
EXPECT_FALSE(sent);
}
// Test send and receive with buffer
TEST_F(RouteTest, SendReceiveBuffer) {
std::string name = generate_unique_ipc_name("route_send_recv_buf");
route sender_r(name.c_str(), sender);
route receiver_r(name.c_str(), receiver);
ASSERT_TRUE(sender_r.valid());
ASSERT_TRUE(receiver_r.valid());
buffer send_buf = make_test_buffer("Hello Route");
std::thread sender_thread([&]() {
bool sent = sender_r.send(send_buf);
EXPECT_TRUE(sent);
});
std::thread receiver_thread([&]() {
buffer recv_buf = receiver_r.recv();
EXPECT_TRUE(check_buffer_content(recv_buf, "Hello Route"));
});
sender_thread.join();
receiver_thread.join();
}
// Test send and receive with string
TEST_F(RouteTest, SendReceiveString) {
std::string name = generate_unique_ipc_name("route_send_recv_str");
route sender_r(name.c_str(), sender);
route receiver_r(name.c_str(), receiver);
ASSERT_TRUE(sender_r.valid());
ASSERT_TRUE(receiver_r.valid());
std::string test_str = "Test String";
std::thread sender_thread([&]() {
bool sent = sender_r.send(test_str);
EXPECT_TRUE(sent);
});
std::thread receiver_thread([&]() {
buffer recv_buf = receiver_r.recv();
EXPECT_TRUE(check_buffer_content(recv_buf, test_str));
});
sender_thread.join();
receiver_thread.join();
}
// Test send and receive with raw data
TEST_F(RouteTest, SendReceiveRawData) {
std::string name = generate_unique_ipc_name("route_send_recv_raw");
route sender_r(name.c_str(), sender);
route receiver_r(name.c_str(), receiver);
ASSERT_TRUE(sender_r.valid());
ASSERT_TRUE(receiver_r.valid());
const char* data = "Raw Data Test";
std::size_t size = std::strlen(data) + 1;
std::thread sender_thread([&]() {
bool sent = sender_r.send(data, size);
EXPECT_TRUE(sent);
});
std::thread receiver_thread([&]() {
buffer recv_buf = receiver_r.recv();
EXPECT_EQ(recv_buf.size(), size);
EXPECT_STREQ(static_cast<const char*>(recv_buf.data()), data);
});
sender_thread.join();
receiver_thread.join();
}
// Test try_recv when empty
TEST_F(RouteTest, TryRecvEmpty) {
std::string name = generate_unique_ipc_name("route_try_recv_empty");
route r(name.c_str(), receiver);
ASSERT_TRUE(r.valid());
buffer buf = r.try_recv();
EXPECT_TRUE(buf.empty());
}
// Test recv_count
TEST_F(RouteTest, RecvCount) {
std::string name = generate_unique_ipc_name("route_recv_count");
route sender_r(name.c_str(), sender);
route receiver_r(name.c_str(), receiver);
ASSERT_TRUE(sender_r.valid());
ASSERT_TRUE(receiver_r.valid());
std::size_t count = sender_r.recv_count();
EXPECT_GE(count, 0u);
}
// Test wait_for_recv
TEST_F(RouteTest, WaitForRecv) {
std::string name = generate_unique_ipc_name("route_wait_recv");
route sender_r(name.c_str(), sender);
std::thread receiver_thread([&]() {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
route receiver_r(name.c_str(), receiver);
});
bool waited = sender_r.wait_for_recv(1, 500);
receiver_thread.join();
// Result depends on timing
}
// Test static wait_for_recv
TEST_F(RouteTest, StaticWaitForRecv) {
std::string name = generate_unique_ipc_name("route_static_wait");
std::thread receiver_thread([&]() {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
route receiver_r(name.c_str(), receiver);
});
bool waited = route::wait_for_recv(name.c_str(), 1, 500);
receiver_thread.join();
}
// Test one sender, multiple receivers
TEST_F(RouteTest, OneSenderMultipleReceivers) {
std::string name = generate_unique_ipc_name("route_1_to_n");
route sender_r(name.c_str(), sender);
ASSERT_TRUE(sender_r.valid());
const int num_receivers = 3;
std::vector<std::atomic<bool>> received(num_receivers);
for (auto& r : received) r.store(false);
std::vector<std::thread> receivers;
for (int i = 0; i < num_receivers; ++i) {
receivers.emplace_back([&, i]() {
route receiver_r(name.c_str(), receiver);
buffer buf = receiver_r.recv(1000);
if (check_buffer_content(buf, "Broadcast")) {
received[i].store(true);
}
});
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
sender_r.send(std::string("Broadcast"));
for (auto& t : receivers) {
t.join();
}
// All receivers should receive the message (broadcast)
for (const auto& r : received) {
EXPECT_TRUE(r.load());
}
}
// ========== Channel Tests (Multiple Producer, Multiple Consumer) ==========
class ChannelTest : public ::testing::Test {
protected:
void TearDown() override {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
};
// Test default construction
TEST_F(ChannelTest, DefaultConstruction) {
channel ch;
EXPECT_FALSE(ch.valid());
}
// Test construction with name
TEST_F(ChannelTest, ConstructionWithName) {
std::string name = generate_unique_ipc_name("channel_ctor");
channel ch(name.c_str(), sender);
EXPECT_TRUE(ch.valid());
EXPECT_STREQ(ch.name(), name.c_str());
}
// Test send and receive
TEST_F(ChannelTest, SendReceive) {
std::string name = generate_unique_ipc_name("channel_send_recv");
channel sender_ch(name.c_str(), sender);
channel receiver_ch(name.c_str(), receiver);
ASSERT_TRUE(sender_ch.valid());
ASSERT_TRUE(receiver_ch.valid());
std::thread sender_thread([&]() {
sender_ch.send(std::string("Channel Test"));
});
std::thread receiver_thread([&]() {
buffer buf = receiver_ch.recv();
EXPECT_TRUE(check_buffer_content(buf, "Channel Test"));
});
sender_thread.join();
receiver_thread.join();
}
// Test multiple senders
TEST_F(ChannelTest, MultipleSenders) {
std::string name = generate_unique_ipc_name("channel_multi_send");
channel receiver_ch(name.c_str(), receiver);
ASSERT_TRUE(receiver_ch.valid());
const int num_senders = 3;
std::atomic<int> received_count{0};
std::vector<std::thread> senders;
for (int i = 0; i < num_senders; ++i) {
senders.emplace_back([&, i]() {
channel sender_ch(name.c_str(), sender);
std::string msg = "Sender" + std::to_string(i);
sender_ch.send(msg);
});
}
std::thread receiver([&]() {
for (int i = 0; i < num_senders; ++i) {
buffer buf = receiver_ch.recv(1000);
if (!buf.empty()) {
++received_count;
}
}
});
for (auto& t : senders) {
t.join();
}
receiver.join();
EXPECT_EQ(received_count.load(), num_senders);
}
// Test multiple senders and receivers
TEST_F(ChannelTest, MultipleSendersReceivers) {
std::string name = generate_unique_ipc_name("channel_m_to_n");
const int num_senders = 2;
const int num_receivers = 2;
const int messages_per_sender = 5;
const int total_messages = num_senders * messages_per_sender; // Each receiver should get all messages
std::atomic<int> sent_count{0};
std::atomic<int> received_count{0};
// Use latch to ensure receivers are ready before senders start
latch receivers_ready(num_receivers);
std::vector<std::thread> receivers;
for (int i = 0; i < num_receivers; ++i) {
receivers.emplace_back([&, i]() {
channel ch(name.c_str(), receiver);
receivers_ready.count_down(); // Signal this receiver is ready
// Each receiver should receive ALL messages from ALL senders (broadcast mode)
for (int j = 0; j < total_messages; ++j) {
buffer buf = ch.recv(2000);
if (!buf.empty()) {
++received_count;
}
}
});
}
// Wait for all receivers to be ready
receivers_ready.wait();
std::vector<std::thread> senders;
for (int i = 0; i < num_senders; ++i) {
senders.emplace_back([&, i]() {
channel ch(name.c_str(), sender);
for (int j = 0; j < messages_per_sender; ++j) {
std::string msg = "S" + std::to_string(i) + "M" + std::to_string(j);
if (ch.send(msg, 1000)) {
++sent_count;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
});
}
for (auto& t : senders) {
t.join();
}
for (auto& t : receivers) {
t.join();
}
EXPECT_EQ(sent_count.load(), num_senders * messages_per_sender);
// All messages should be received (broadcast mode)
EXPECT_EQ(received_count.load(), num_senders * messages_per_sender * num_receivers);
}
// Test try_send and try_recv
TEST_F(ChannelTest, TrySendTryRecv) {
std::string name = generate_unique_ipc_name("channel_try");
channel sender_ch(name.c_str(), sender);
channel receiver_ch(name.c_str(), receiver);
ASSERT_TRUE(sender_ch.valid());
ASSERT_TRUE(receiver_ch.valid());
bool sent = sender_ch.try_send(std::string("Try Test"));
if (sent) {
buffer buf = receiver_ch.try_recv();
EXPECT_FALSE(buf.empty());
}
}
// Test timeout scenarios
TEST_F(ChannelTest, SendTimeout) {
std::string name = generate_unique_ipc_name("channel_timeout");
channel ch(name.c_str(), sender);
ASSERT_TRUE(ch.valid());
// Send with very short timeout (may fail without receiver)
bool sent = ch.send(std::string("Timeout Test"), 1);
}
// Test clear and clear_storage
TEST_F(ChannelTest, ClearStorage) {
std::string name = generate_unique_ipc_name("channel_clear");
{
channel ch(name.c_str(), sender);
EXPECT_TRUE(ch.valid());
}
channel::clear_storage(name.c_str());
}
// Test handle() method
TEST_F(ChannelTest, Handle) {
std::string name = generate_unique_ipc_name("channel_handle");
channel ch(name.c_str(), sender);
ASSERT_TRUE(ch.valid());
handle_t h = ch.handle();
EXPECT_NE(h, nullptr);
}

View File

@ -1,613 +0,0 @@
/**
* @file test_locks.cpp
* @brief Comprehensive unit tests for ipc::rw_lock and ipc::spin_lock classes
*
* This test suite covers:
* - spin_lock: basic lock/unlock operations
* - rw_lock: read-write lock functionality
* - rw_lock: exclusive (write) locks
* - rw_lock: shared (read) locks
* - Concurrent access patterns
* - Reader-writer scenarios
*/
#include <gtest/gtest.h>
#include <thread>
#include <chrono>
#include <atomic>
#include <vector>
#include "libipc/rw_lock.h"
using namespace ipc;
// ========== spin_lock Tests ==========
class SpinLockTest : public ::testing::Test {
protected:
void TearDown() override {
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
};
// Test basic lock and unlock
TEST_F(SpinLockTest, BasicLockUnlock) {
spin_lock lock;
lock.lock();
lock.unlock();
// Should complete without hanging
}
// Test multiple lock/unlock cycles
TEST_F(SpinLockTest, MultipleCycles) {
spin_lock lock;
for (int i = 0; i < 100; ++i) {
lock.lock();
lock.unlock();
}
}
// Test critical section protection
TEST_F(SpinLockTest, CriticalSection) {
spin_lock lock;
int counter = 0;
const int iterations = 1000;
auto increment_task = [&]() {
for (int i = 0; i < iterations; ++i) {
lock.lock();
++counter;
lock.unlock();
}
};
std::thread t1(increment_task);
std::thread t2(increment_task);
t1.join();
t2.join();
EXPECT_EQ(counter, iterations * 2);
}
// Test mutual exclusion
TEST_F(SpinLockTest, MutualExclusion) {
spin_lock lock;
std::atomic<bool> thread1_in_cs{false};
std::atomic<bool> thread2_in_cs{false};
std::atomic<bool> violation{false};
auto cs_task = [&](std::atomic<bool>& my_flag, std::atomic<bool>& other_flag) {
for (int i = 0; i < 100; ++i) {
lock.lock();
my_flag.store(true);
if (other_flag.load()) {
violation.store(true);
}
std::this_thread::sleep_for(std::chrono::microseconds(10));
my_flag.store(false);
lock.unlock();
std::this_thread::yield();
}
};
std::thread t1(cs_task, std::ref(thread1_in_cs), std::ref(thread2_in_cs));
std::thread t2(cs_task, std::ref(thread2_in_cs), std::ref(thread1_in_cs));
t1.join();
t2.join();
EXPECT_FALSE(violation.load());
}
// Test concurrent access
TEST_F(SpinLockTest, ConcurrentAccess) {
spin_lock lock;
std::atomic<int> shared_data{0};
const int num_threads = 4;
const int ops_per_thread = 100;
std::vector<std::thread> threads;
for (int i = 0; i < num_threads; ++i) {
threads.emplace_back([&]() {
for (int j = 0; j < ops_per_thread; ++j) {
lock.lock();
int temp = shared_data.load();
std::this_thread::yield();
shared_data.store(temp + 1);
lock.unlock();
}
});
}
for (auto& t : threads) {
t.join();
}
EXPECT_EQ(shared_data.load(), num_threads * ops_per_thread);
}
// Test rapid lock/unlock
TEST_F(SpinLockTest, RapidLockUnlock) {
spin_lock lock;
auto rapid_task = [&]() {
for (int i = 0; i < 10000; ++i) {
lock.lock();
lock.unlock();
}
};
std::thread t1(rapid_task);
std::thread t2(rapid_task);
t1.join();
t2.join();
// Should complete without deadlock
}
// Test contention scenario
TEST_F(SpinLockTest, Contention) {
spin_lock lock;
std::atomic<int> work_done{0};
const int num_threads = 8;
std::vector<std::thread> threads;
for (int i = 0; i < num_threads; ++i) {
threads.emplace_back([&]() {
for (int j = 0; j < 50; ++j) {
lock.lock();
++work_done;
std::this_thread::sleep_for(std::chrono::microseconds(100));
lock.unlock();
std::this_thread::yield();
}
});
}
for (auto& t : threads) {
t.join();
}
EXPECT_EQ(work_done.load(), num_threads * 50);
}
// ========== rw_lock Tests ==========
class RWLockTest : public ::testing::Test {
protected:
void TearDown() override {
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
};
// Test basic write lock and unlock
TEST_F(RWLockTest, BasicWriteLock) {
rw_lock lock;
lock.lock();
lock.unlock();
// Should complete without hanging
}
// Test basic read lock and unlock
TEST_F(RWLockTest, BasicReadLock) {
rw_lock lock;
lock.lock_shared();
lock.unlock_shared();
// Should complete without hanging
}
// Test multiple write cycles
TEST_F(RWLockTest, MultipleWriteCycles) {
rw_lock lock;
for (int i = 0; i < 100; ++i) {
lock.lock();
lock.unlock();
}
}
// Test multiple read cycles
TEST_F(RWLockTest, MultipleReadCycles) {
rw_lock lock;
for (int i = 0; i < 100; ++i) {
lock.lock_shared();
lock.unlock_shared();
}
}
// Test write lock protects data
TEST_F(RWLockTest, WriteLockProtection) {
rw_lock lock;
int data = 0;
const int iterations = 500;
auto writer_task = [&]() {
for (int i = 0; i < iterations; ++i) {
lock.lock();
++data;
lock.unlock();
}
};
std::thread t1(writer_task);
std::thread t2(writer_task);
t1.join();
t2.join();
EXPECT_EQ(data, iterations * 2);
}
// Test multiple readers can access concurrently
TEST_F(RWLockTest, ConcurrentReaders) {
rw_lock lock;
std::atomic<int> concurrent_readers{0};
std::atomic<int> max_concurrent{0};
const int num_readers = 5;
std::vector<std::thread> readers;
for (int i = 0; i < num_readers; ++i) {
readers.emplace_back([&]() {
for (int j = 0; j < 20; ++j) {
lock.lock_shared();
int current = ++concurrent_readers;
// Track maximum concurrent readers
int current_max = max_concurrent.load();
while (current > current_max) {
if (max_concurrent.compare_exchange_weak(current_max, current)) {
break;
}
}
std::this_thread::sleep_for(std::chrono::microseconds(100));
--concurrent_readers;
lock.unlock_shared();
std::this_thread::yield();
}
});
}
for (auto& t : readers) {
t.join();
}
// Should have had multiple concurrent readers
EXPECT_GT(max_concurrent.load(), 1);
}
// Test writers have exclusive access
TEST_F(RWLockTest, WriterExclusiveAccess) {
rw_lock lock;
std::atomic<bool> writer_in_cs{false};
std::atomic<bool> violation{false};
auto writer_task = [&]() {
for (int i = 0; i < 50; ++i) {
lock.lock();
if (writer_in_cs.exchange(true)) {
violation.store(true);
}
std::this_thread::sleep_for(std::chrono::microseconds(50));
writer_in_cs.store(false);
lock.unlock();
std::this_thread::yield();
}
};
std::thread t1(writer_task);
std::thread t2(writer_task);
t1.join();
t2.join();
EXPECT_FALSE(violation.load());
}
// Test readers and writers don't overlap
TEST_F(RWLockTest, ReadersWritersNoOverlap) {
rw_lock lock;
std::atomic<int> readers{0};
std::atomic<bool> writer_active{false};
std::atomic<bool> violation{false};
auto reader_task = [&]() {
for (int i = 0; i < 30; ++i) {
lock.lock_shared();
++readers;
if (writer_active.load()) {
violation.store(true);
}
std::this_thread::sleep_for(std::chrono::microseconds(50));
--readers;
lock.unlock_shared();
std::this_thread::yield();
}
};
auto writer_task = [&]() {
for (int i = 0; i < 15; ++i) {
lock.lock();
writer_active.store(true);
if (readers.load() > 0) {
violation.store(true);
}
std::this_thread::sleep_for(std::chrono::microseconds(50));
writer_active.store(false);
lock.unlock();
std::this_thread::yield();
}
};
std::thread r1(reader_task);
std::thread r2(reader_task);
std::thread w1(writer_task);
r1.join();
r2.join();
w1.join();
EXPECT_FALSE(violation.load());
}
// Test read-write-read pattern
TEST_F(RWLockTest, ReadWriteReadPattern) {
rw_lock lock;
int data = 0;
std::atomic<int> iterations{0};
auto pattern_task = [&](int id) {
for (int i = 0; i < 20; ++i) {
// Write: increment based on thread id
lock.lock();
data += id;
lock.unlock();
iterations.fetch_add(1);
std::this_thread::yield();
// Read: verify data is consistent
lock.lock_shared();
int read_val = data;
EXPECT_GE(read_val, 0); // Data should be non-negative
lock.unlock_shared();
std::this_thread::yield();
}
};
std::thread t1(pattern_task, 1);
std::thread t2(pattern_task, 2);
t1.join();
t2.join();
// Each thread increments by its id (1 or 2), 20 times each
// Total = 1*20 + 2*20 = 20 + 40 = 60
EXPECT_EQ(data, 60);
EXPECT_EQ(iterations.load(), 40);
}
// Test many readers, one writer
TEST_F(RWLockTest, ManyReadersOneWriter) {
rw_lock lock;
std::atomic<int> data{0};
std::atomic<int> read_count{0};
const int num_readers = 10;
std::vector<std::thread> readers;
for (int i = 0; i < num_readers; ++i) {
readers.emplace_back([&]() {
for (int j = 0; j < 50; ++j) {
lock.lock_shared();
int val = data.load();
++read_count;
lock.unlock_shared();
std::this_thread::yield();
}
});
}
std::thread writer([&]() {
for (int i = 0; i < 100; ++i) {
lock.lock();
data.store(data.load() + 1);
lock.unlock();
std::this_thread::yield();
}
});
for (auto& t : readers) {
t.join();
}
writer.join();
EXPECT_EQ(data.load(), 100);
EXPECT_EQ(read_count.load(), num_readers * 50);
}
// Test rapid read lock/unlock
TEST_F(RWLockTest, RapidReadLocks) {
rw_lock lock;
auto rapid_read = [&]() {
for (int i = 0; i < 5000; ++i) {
lock.lock_shared();
lock.unlock_shared();
}
};
std::thread t1(rapid_read);
std::thread t2(rapid_read);
std::thread t3(rapid_read);
t1.join();
t2.join();
t3.join();
}
// Test rapid write lock/unlock
TEST_F(RWLockTest, RapidWriteLocks) {
rw_lock lock;
auto rapid_write = [&]() {
for (int i = 0; i < 2000; ++i) {
lock.lock();
lock.unlock();
}
};
std::thread t1(rapid_write);
std::thread t2(rapid_write);
t1.join();
t2.join();
}
// Test mixed rapid operations
TEST_F(RWLockTest, MixedRapidOperations) {
rw_lock lock;
auto rapid_read = [&]() {
for (int i = 0; i < 1000; ++i) {
lock.lock_shared();
lock.unlock_shared();
}
};
auto rapid_write = [&]() {
for (int i = 0; i < 500; ++i) {
lock.lock();
lock.unlock();
}
};
std::thread r1(rapid_read);
std::thread r2(rapid_read);
std::thread w1(rapid_write);
r1.join();
r2.join();
w1.join();
}
// Test write lock doesn't allow concurrent readers
TEST_F(RWLockTest, WriteLockBlocksReaders) {
rw_lock lock;
std::atomic<bool> write_locked{false};
std::atomic<bool> reader_entered{false};
std::thread writer([&]() {
lock.lock();
write_locked.store(true);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
write_locked.store(false);
lock.unlock();
});
std::thread reader([&]() {
std::this_thread::sleep_for(std::chrono::milliseconds(20));
lock.lock_shared();
if (write_locked.load()) {
reader_entered.store(true);
}
lock.unlock_shared();
});
writer.join();
reader.join();
// Reader should not have entered while writer held the lock
EXPECT_FALSE(reader_entered.load());
}
// Test multiple write lock upgrades
TEST_F(RWLockTest, MultipleWriteLockPattern) {
rw_lock lock;
int data = 0;
for (int i = 0; i < 100; ++i) {
// Read
lock.lock_shared();
int temp = data;
lock.unlock_shared();
// Write
lock.lock();
data = temp + 1;
lock.unlock();
}
EXPECT_EQ(data, 100);
}
// Test concurrent mixed operations
TEST_F(RWLockTest, ConcurrentMixedOperations) {
rw_lock lock;
std::atomic<int> data{0};
std::atomic<int> reads{0};
std::atomic<int> writes{0};
auto mixed_task = [&](int id) {
for (int i = 0; i < 50; ++i) {
if (i % 3 == 0) {
// Write operation
lock.lock();
data.store(data.load() + 1);
++writes;
lock.unlock();
} else {
// Read operation
lock.lock_shared();
int val = data.load();
++reads;
lock.unlock_shared();
}
std::this_thread::yield();
}
};
std::thread t1(mixed_task, 1);
std::thread t2(mixed_task, 2);
std::thread t3(mixed_task, 3);
std::thread t4(mixed_task, 4);
t1.join();
t2.join();
t3.join();
t4.join();
EXPECT_GT(reads.load(), 0);
EXPECT_GT(writes.load(), 0);
}

Some files were not shown because too many files have changed in this diff Show More