mirror of
https://github.com/mutouyun/cpp-ipc.git
synced 2025-12-07 01:06:45 +08:00
Compare commits
No commits in common. "master" and "v1.0.0" have entirely different histories.
1
.gitignore
vendored
1
.gitignore
vendored
@ -28,7 +28,6 @@ ui_*.h
|
|||||||
*.jsc
|
*.jsc
|
||||||
Makefile*
|
Makefile*
|
||||||
*build-*
|
*build-*
|
||||||
*build_*
|
|
||||||
|
|
||||||
# Qt unit tests
|
# Qt unit tests
|
||||||
target_wrapper.*
|
target_wrapper.*
|
||||||
|
|||||||
31
.travis.yml
Executable file
31
.travis.yml
Executable 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
|
||||||
4
3rdparty/gtest/src/gtest-death-test.cc
vendored
4
3rdparty/gtest/src/gtest-death-test.cc
vendored
@ -1296,8 +1296,8 @@ static void StackLowerThanAddress(const void* ptr, bool* result) {
|
|||||||
GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
|
GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
|
||||||
GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_
|
GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_
|
||||||
static bool StackGrowsDown() {
|
static bool StackGrowsDown() {
|
||||||
int dummy {};
|
int dummy;
|
||||||
bool result {};
|
bool result;
|
||||||
StackLowerThanAddress(&dummy, &result);
|
StackLowerThanAddress(&dummy, &result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ if(NOT MSVC)
|
|||||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
|
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (MSVC)
|
if (MSVC AND LIBIPC_USE_STATIC_CRT)
|
||||||
set(CompilerFlags
|
set(CompilerFlags
|
||||||
CMAKE_CXX_FLAGS
|
CMAKE_CXX_FLAGS
|
||||||
CMAKE_CXX_FLAGS_DEBUG
|
CMAKE_CXX_FLAGS_DEBUG
|
||||||
@ -22,17 +22,9 @@ if (MSVC)
|
|||||||
CMAKE_C_FLAGS_DEBUG
|
CMAKE_C_FLAGS_DEBUG
|
||||||
CMAKE_C_FLAGS_RELEASE
|
CMAKE_C_FLAGS_RELEASE
|
||||||
)
|
)
|
||||||
if (LIBIPC_USE_STATIC_CRT)
|
foreach(CompilerFlag ${CompilerFlags})
|
||||||
foreach(CompilerFlag ${CompilerFlags})
|
string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}")
|
||||||
string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}")
|
endforeach()
|
||||||
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()
|
endif()
|
||||||
|
|
||||||
set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin)
|
set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin)
|
||||||
@ -58,14 +50,6 @@ endif()
|
|||||||
if (LIBIPC_BUILD_DEMOS)
|
if (LIBIPC_BUILD_DEMOS)
|
||||||
add_subdirectory(demo/chat)
|
add_subdirectory(demo/chat)
|
||||||
add_subdirectory(demo/msg_que)
|
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()
|
endif()
|
||||||
|
|
||||||
install(
|
install(
|
||||||
|
|||||||
70
README.md
70
README.md
@ -1,21 +1,20 @@
|
|||||||
# cpp-ipc (libipc) - C++ IPC Library
|
# cpp-ipc(libipc) - C++ IPC Library
|
||||||
|
|
||||||
[](https://github.com/mutouyun/cpp-ipc/blob/master/LICENSE)
|
[](https://github.com/mutouyun/cpp-ipc/blob/master/LICENSE)
|
||||||
[](https://github.com/mutouyun/cpp-ipc/actions)
|
[](https://github.com/mutouyun/cpp-ipc/actions)
|
||||||
[](https://ci.appveyor.com/project/mutouyun/cpp-ipc)
|
[](https://ci.appveyor.com/project/mutouyun/cpp-ipc)
|
||||||
[](https://github.com/microsoft/vcpkg/tree/master/ports/cpp-ipc)
|
|
||||||
|
A high-performance inter-process communication using shared memory on Linux/Windows.
|
||||||
## A high-performance inter-process communication library using shared memory on Linux/Windows/FreeBSD.
|
使用共享内存的跨平台(Linux/Windows,x86/x64/ARM)高性能IPC通讯库。
|
||||||
|
|
||||||
* Compilers with C++17 support are recommended (msvc-2017/gcc-7/clang-4)
|
* 推荐支持C++17的编译器(msvc-2017/gcc-7/clang-4)
|
||||||
* No other dependencies except STL.
|
* 除STL外,无其他依赖
|
||||||
* Only lock-free or lightweight spin-lock is used.
|
* 无锁(lock-free)或轻量级spin-lock
|
||||||
* Circular array is used as the underline data structure.
|
* 底层数据结构为循环数组(circular array)
|
||||||
* `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.**)
|
* `ipc::route`支持单写多读,`ipc::channel`支持多写多读【**注意:目前同一条通道最多支持32个receiver,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`
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
See: [Wiki](https://github.com/mutouyun/cpp-ipc/wiki)
|
See: [Wiki](https://github.com/mutouyun/cpp-ipc/wiki)
|
||||||
@ -30,7 +29,7 @@ See: [Wiki](https://github.com/mutouyun/cpp-ipc/wiki)
|
|||||||
OS | Windows 7 Ultimate x64
|
OS | Windows 7 Ultimate x64
|
||||||
Compiler | MSVC 2017 15.9.4
|
Compiler | MSVC 2017 15.9.4
|
||||||
|
|
||||||
Unit & benchmark tests: [test](test)
|
UT & benchmark test function: [test](test)
|
||||||
Performance data: [performance.xlsx](performance.xlsx)
|
Performance data: [performance.xlsx](performance.xlsx)
|
||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
@ -40,42 +39,3 @@ Performance data: [performance.xlsx](performance.xlsx)
|
|||||||
* [Lock-Free 编程 | 匠心十年 - 博客园](http://www.cnblogs.com/gaochundong/p/lock_free_programming.html)
|
* [Lock-Free 编程 | 匠心十年 - 博客园](http://www.cnblogs.com/gaochundong/p/lock_free_programming.html)
|
||||||
* [无锁队列的实现 | 酷 壳 - CoolShell](https://coolshell.cn/articles/8239.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)
|
* [Implementing Condition Variables with Semaphores](https://www.microsoft.com/en-us/research/wp-content/uploads/2004/12/ImplementingCVs.pdf)
|
||||||
|
|
||||||
------
|
|
||||||
|
|
||||||
|
|
||||||
## 使用共享内存的跨平台(Linux/Windows/FreeBSD,x86/x64/ARM)高性能IPC通讯库
|
|
||||||
|
|
||||||
* 推荐支持C++17的编译器(msvc-2017/gcc-7/clang-4)
|
|
||||||
* 除STL外,无其他依赖
|
|
||||||
* 无锁(lock-free)或轻量级spin-lock
|
|
||||||
* 底层数据结构为循环数组(circular array)
|
|
||||||
* `ipc::route`支持单写多读,`ipc::channel`支持多写多读【**注意:目前同一条通道最多支持32个receiver,sender无限制**】
|
|
||||||
* 默认采用广播模式收发数据,支持用户任意选择读写方案
|
|
||||||
* 不会长时间忙等(重试一定次数后会使用信号量进行等待),支持超时
|
|
||||||
* 支持[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)
|
|
||||||
|
|||||||
@ -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)
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -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)
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -20,8 +20,8 @@ constexpr char const mode_r__[] = "r";
|
|||||||
constexpr std::size_t const min_sz = 128;
|
constexpr std::size_t const min_sz = 128;
|
||||||
constexpr std::size_t const max_sz = 1024 * 16;
|
constexpr std::size_t const max_sz = 1024 * 16;
|
||||||
|
|
||||||
std::atomic<bool> is_quit__ {false};
|
std::atomic<bool> is_quit__{ false };
|
||||||
std::atomic<std::size_t> size_counter__ {0};
|
std::atomic<std::size_t> size_counter__{ 0 };
|
||||||
|
|
||||||
using msg_que_t = ipc::chan<ipc::relat::single, ipc::relat::multi, ipc::trans::broadcast>;
|
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);
|
::signal(SIGHUP , exit);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::string mode {argv[1]};
|
if (std::string{ argv[1] } == mode_s__) {
|
||||||
if (mode == mode_s__) {
|
|
||||||
do_send();
|
do_send();
|
||||||
} else if (mode == mode_r__) {
|
}
|
||||||
|
else if (std::string{ argv[1] } == mode_r__) {
|
||||||
do_recv();
|
do_recv();
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@ -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)
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -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)
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -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)
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -17,16 +17,14 @@ public:
|
|||||||
buffer();
|
buffer();
|
||||||
|
|
||||||
buffer(void* p, std::size_t s, destructor_t d);
|
buffer(void* p, std::size_t s, destructor_t d);
|
||||||
// mem_to_free: pointer to be passed to destructor (if different from p)
|
buffer(void* p, std::size_t s, destructor_t d, void* additional);
|
||||||
// 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);
|
buffer(void* p, std::size_t s);
|
||||||
|
|
||||||
template <std::size_t N>
|
template <std::size_t N>
|
||||||
explicit buffer(byte_t (& data)[N])
|
explicit buffer(byte_t const (& data)[N])
|
||||||
: buffer(data, sizeof(data)) {
|
: buffer(data, sizeof(data)) {
|
||||||
}
|
}
|
||||||
explicit buffer(char & c);
|
explicit buffer(char const & c);
|
||||||
|
|
||||||
buffer(buffer&& rhs);
|
buffer(buffer&& rhs);
|
||||||
~buffer();
|
~buffer();
|
||||||
|
|||||||
@ -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
|
|
||||||
@ -25,16 +25,13 @@ using uint_t = typename uint<N>::type;
|
|||||||
|
|
||||||
// constants
|
// constants
|
||||||
|
|
||||||
enum : std::uint32_t {
|
|
||||||
invalid_value = (std::numeric_limits<std::uint32_t>::max)(),
|
|
||||||
default_timeout = 100, // ms
|
|
||||||
};
|
|
||||||
|
|
||||||
enum : std::size_t {
|
enum : std::size_t {
|
||||||
|
invalid_value = (std::numeric_limits<std::size_t>::max)(),
|
||||||
data_length = 64,
|
data_length = 64,
|
||||||
large_msg_limit = data_length,
|
large_msg_limit = data_length,
|
||||||
large_msg_align = 1024,
|
large_msg_align = 1024,
|
||||||
large_msg_cache = 32,
|
large_msg_cache = 32,
|
||||||
|
default_timeout = 100 // ms
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class relat { // multiplicity of the relationship
|
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>
|
template <template <typename> class Policy, typename Flag>
|
||||||
struct relat_trait<Policy<Flag>> : relat_trait<Flag> {};
|
struct relat_trait<Policy<Flag>> : relat_trait<Flag> {};
|
||||||
|
|
||||||
// the prefix tag of a channel
|
|
||||||
struct prefix {
|
|
||||||
char const *str;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ipc
|
} // namespace ipc
|
||||||
|
|||||||
@ -19,31 +19,20 @@ enum : unsigned {
|
|||||||
|
|
||||||
template <typename Flag>
|
template <typename Flag>
|
||||||
struct IPC_EXPORT chan_impl {
|
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, 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 bool reconnect (ipc::handle_t * ph, unsigned mode);
|
||||||
static void disconnect(ipc::handle_t h);
|
static void disconnect(ipc::handle_t h);
|
||||||
static void destroy (ipc::handle_t h);
|
static void destroy (ipc::handle_t h);
|
||||||
|
|
||||||
static char const * name(ipc::handle_t h);
|
static char const * name(ipc::handle_t h);
|
||||||
|
|
||||||
// Release memory without waiting for the connection to disconnect.
|
static std::size_t recv_count(ipc::handle_t h);
|
||||||
static void release(ipc::handle_t h) noexcept;
|
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 bool send(ipc::handle_t h, void const * data, std::size_t size, std::size_t tm);
|
||||||
static void clear(ipc::handle_t h) noexcept;
|
static buff_t recv(ipc::handle_t h, std::size_t tm);
|
||||||
static void clear_storage(char const * name) noexcept;
|
|
||||||
static void clear_storage(prefix, char const * name) noexcept;
|
|
||||||
|
|
||||||
static std::size_t recv_count (ipc::handle_t h);
|
static bool try_send(ipc::handle_t h, void const * data, std::size_t size, std::size_t tm);
|
||||||
static bool wait_for_recv(ipc::handle_t h, std::size_t r_count, std::uint64_t tm);
|
|
||||||
|
|
||||||
static bool send(ipc::handle_t h, void const * data, std::size_t size, std::uint64_t tm);
|
|
||||||
static buff_t recv(ipc::handle_t h, std::uint64_t tm);
|
|
||||||
|
|
||||||
static bool try_send(ipc::handle_t h, void const * data, std::size_t size, std::uint64_t tm);
|
|
||||||
static buff_t try_recv(ipc::handle_t h);
|
static buff_t try_recv(ipc::handle_t h);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -52,7 +41,7 @@ class chan_wrapper {
|
|||||||
private:
|
private:
|
||||||
using detail_t = chan_impl<Flag>;
|
using detail_t = chan_impl<Flag>;
|
||||||
|
|
||||||
ipc::handle_t h_ = detail_t::init_first();
|
ipc::handle_t h_ = nullptr;
|
||||||
unsigned mode_ = ipc::sender;
|
unsigned mode_ = ipc::sender;
|
||||||
bool connected_ = false;
|
bool connected_ = false;
|
||||||
|
|
||||||
@ -63,10 +52,6 @@ public:
|
|||||||
: connected_{this->connect(name, mode)} {
|
: 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(chan_wrapper&& rhs) noexcept
|
||||||
: chan_wrapper{} {
|
: chan_wrapper{} {
|
||||||
swap(rhs);
|
swap(rhs);
|
||||||
@ -91,28 +76,6 @@ public:
|
|||||||
return detail_t::name(h_);
|
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 {
|
ipc::handle_t handle() const noexcept {
|
||||||
return h_;
|
return h_;
|
||||||
}
|
}
|
||||||
@ -137,11 +100,6 @@ public:
|
|||||||
detail_t::disconnect(h_); // clear old connection
|
detail_t::disconnect(h_); // clear old connection
|
||||||
return connected_ = detail_t::connect(&h_, name, mode_ = mode);
|
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.
|
* Try connecting with new mode flags.
|
||||||
@ -162,41 +120,41 @@ public:
|
|||||||
return detail_t::recv_count(h_);
|
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);
|
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);
|
return chan_wrapper(name).wait_for_recv(r_count, tm);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If timeout, this function would call 'force_push' to send the data forcibly.
|
* 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);
|
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);
|
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);
|
return this->send(str.c_str(), str.size() + 1, tm);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If timeout, this function would just return false.
|
* 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);
|
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);
|
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);
|
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);
|
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>>;
|
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,
|
* then all the consumers/clients/receivers which are receiving with this route,
|
||||||
* would receive your sent messages.
|
* 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>;
|
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,
|
* then all the consumers/readers which are receiving with this channel,
|
||||||
* would receive your sent messages.
|
* would receive your sent messages.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using channel = chan<relat::multi, relat::multi, trans::broadcast>;
|
using channel = chan<relat::multi, relat::multi, trans::broadcast>;
|
||||||
|
|
||||||
} // namespace ipc
|
} // namespace ipc
|
||||||
|
|||||||
@ -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
|
|
||||||
@ -11,8 +11,8 @@ namespace mem {
|
|||||||
|
|
||||||
class IPC_EXPORT pool_alloc {
|
class IPC_EXPORT pool_alloc {
|
||||||
public:
|
public:
|
||||||
static void* alloc(std::size_t size) noexcept;
|
static void* alloc(std::size_t size);
|
||||||
static void free (void* p, std::size_t size) noexcept;
|
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>
|
template <typename T>
|
||||||
void free(T* p) {
|
void free(T* p) {
|
||||||
if (p == nullptr) return;
|
|
||||||
destruct(p);
|
destruct(p);
|
||||||
pool_alloc::free(p, sizeof(T));
|
pool_alloc::free(p, sizeof(T));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,6 @@
|
|||||||
#include <limits>
|
#include <limits>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
/// Gives hint to processor that improves performance of spin-wait loops.
|
/// Gives hint to processor that improves performance of spin-wait loops.
|
||||||
@ -73,7 +72,7 @@ inline void yield(K& k) noexcept {
|
|||||||
++k;
|
++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) {
|
inline void sleep(K& k, F&& f) {
|
||||||
if (k < static_cast<K>(N)) {
|
if (k < static_cast<K>(N)) {
|
||||||
std::this_thread::yield();
|
std::this_thread::yield();
|
||||||
@ -85,7 +84,7 @@ inline void sleep(K& k, F&& f) {
|
|||||||
++k;
|
++k;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <std::size_t N = 32, typename K>
|
template <std::size_t N = 4096, typename K>
|
||||||
inline void sleep(K& k) {
|
inline void sleep(K& k) {
|
||||||
sleep<N>(k, [] {
|
sleep<N>(k, [] {
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
@ -99,7 +98,7 @@ inline void sleep(K& k) {
|
|||||||
namespace ipc {
|
namespace ipc {
|
||||||
|
|
||||||
class spin_lock {
|
class spin_lock {
|
||||||
std::atomic<std::uint32_t> lc_ { 0 };
|
std::atomic<unsigned> lc_ { 0 };
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void lock(void) noexcept {
|
void lock(void) noexcept {
|
||||||
@ -114,13 +113,13 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
class rw_lock {
|
class rw_lock {
|
||||||
using lc_ui_t = std::uint32_t;
|
using lc_ui_t = unsigned;
|
||||||
|
|
||||||
std::atomic<lc_ui_t> lc_ { 0 };
|
std::atomic<lc_ui_t> lc_ { 0 };
|
||||||
|
|
||||||
enum : lc_ui_t {
|
enum : lc_ui_t {
|
||||||
w_mask = (std::numeric_limits<std::make_signed_t<lc_ui_t>>::max)(), // b 0111 1111 ...
|
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_flag = w_mask + 1 // b 1000 0000
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@ -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
|
|
||||||
@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
#include "libipc/export.h"
|
#include "libipc/export.h"
|
||||||
|
|
||||||
@ -15,36 +14,11 @@ enum : unsigned {
|
|||||||
open = 0x02
|
open = 0x02
|
||||||
};
|
};
|
||||||
|
|
||||||
IPC_EXPORT id_t acquire(char const * name, std::size_t size, unsigned mode = create | open);
|
IPC_EXPORT id_t acquire(char const * name, std::size_t size, unsigned mode = create | open);
|
||||||
IPC_EXPORT void * get_mem(id_t id, std::size_t * size);
|
IPC_EXPORT void * get_mem(id_t id, std::size_t * size);
|
||||||
|
IPC_EXPORT void release(id_t id);
|
||||||
// Release shared memory resource and clean up disk file if reference count reaches zero.
|
IPC_EXPORT void remove (id_t id);
|
||||||
// This function decrements the reference counter. When the counter reaches zero, it:
|
IPC_EXPORT void remove (char const * name);
|
||||||
// 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);
|
|
||||||
|
|
||||||
class IPC_EXPORT handle {
|
class IPC_EXPORT handle {
|
||||||
public:
|
public:
|
||||||
@ -57,19 +31,12 @@ public:
|
|||||||
void swap(handle& rhs);
|
void swap(handle& rhs);
|
||||||
handle& operator=(handle rhs);
|
handle& operator=(handle rhs);
|
||||||
|
|
||||||
bool valid() const noexcept;
|
bool valid() const;
|
||||||
std::size_t size () const noexcept;
|
std::size_t size () const;
|
||||||
char const * name () const noexcept;
|
char const * name () const;
|
||||||
|
|
||||||
std::int32_t ref() const noexcept;
|
|
||||||
void sub_ref() noexcept;
|
|
||||||
|
|
||||||
bool acquire(char const * name, std::size_t size, unsigned mode = create | open);
|
bool acquire(char const * name, std::size_t size, unsigned mode = create | open);
|
||||||
std::int32_t release();
|
void release();
|
||||||
|
|
||||||
// Clean the handle file.
|
|
||||||
void clear() noexcept;
|
|
||||||
static void clear_storage(char const * name) noexcept;
|
|
||||||
|
|
||||||
void* get() const;
|
void* get() const;
|
||||||
|
|
||||||
|
|||||||
93
include/libipc/waiter.h
Executable file
93
include/libipc/waiter.h
Executable 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
|
||||||
BIN
performance.xlsx
BIN
performance.xlsx
Binary file not shown.
@ -1,10 +1,11 @@
|
|||||||
project(ipc)
|
project(ipc)
|
||||||
|
|
||||||
set (PACKAGE_VERSION 1.3.0)
|
if(UNIX)
|
||||||
|
file(GLOB SRC_FILES ${LIBIPC_PROJECT_DIR}/src/libipc/platform/*_linux.cpp)
|
||||||
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc SRC_FILES)
|
else()
|
||||||
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/sync SRC_FILES)
|
file(GLOB SRC_FILES ${LIBIPC_PROJECT_DIR}/src/libipc/platform/*_win.cpp)
|
||||||
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/platform SRC_FILES)
|
endif()
|
||||||
|
aux_source_directory(${LIBIPC_PROJECT_DIR}/src SRC_FILES)
|
||||||
|
|
||||||
file(GLOB HEAD_FILES
|
file(GLOB HEAD_FILES
|
||||||
${LIBIPC_PROJECT_DIR}/include/libipc/*.h
|
${LIBIPC_PROJECT_DIR}/include/libipc/*.h
|
||||||
@ -31,51 +32,28 @@ set_target_properties(${PROJECT_NAME}
|
|||||||
PROPERTIES
|
PROPERTIES
|
||||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||||
LIBRARY_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 version
|
||||||
set_target_properties(${PROJECT_NAME}
|
set_target_properties(${PROJECT_NAME}
|
||||||
PROPERTIES
|
PROPERTIES
|
||||||
VERSION ${PACKAGE_VERSION}
|
VERSION 1.0.0
|
||||||
SOVERSION 3)
|
SOVERSION 1)
|
||||||
|
|
||||||
target_include_directories(${PROJECT_NAME}
|
target_include_directories(${PROJECT_NAME}
|
||||||
PUBLIC
|
PUBLIC ${LIBIPC_PROJECT_DIR}/include
|
||||||
"$<BUILD_INTERFACE:${LIBIPC_PROJECT_DIR}/include>"
|
|
||||||
"$<INSTALL_INTERFACE:include>"
|
|
||||||
PRIVATE ${LIBIPC_PROJECT_DIR}/src
|
PRIVATE ${LIBIPC_PROJECT_DIR}/src
|
||||||
$<$<BOOL:UNIX>:${LIBIPC_PROJECT_DIR}/src/libipc/platform/linux>)
|
)
|
||||||
|
|
||||||
if(NOT MSVC)
|
if(NOT MSVC)
|
||||||
target_link_libraries(${PROJECT_NAME} PUBLIC
|
target_link_libraries(${PROJECT_NAME} PUBLIC
|
||||||
$<$<NOT:$<STREQUAL:${CMAKE_SYSTEM_NAME},QNX>>:pthread>
|
pthread
|
||||||
$<$<NOT:$<OR:$<STREQUAL:${CMAKE_SYSTEM_NAME},Windows>,$<STREQUAL:${CMAKE_SYSTEM_NAME},QNX>>>:rt>)
|
$<$<NOT:$<STREQUAL:${CMAKE_SYSTEM_NAME},Windows>>:rt>)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
install(
|
install(
|
||||||
TARGETS ${PROJECT_NAME}
|
TARGETS ${PROJECT_NAME}
|
||||||
EXPORT cpp-ipc-targets
|
|
||||||
RUNTIME DESTINATION bin
|
RUNTIME DESTINATION bin
|
||||||
LIBRARY DESTINATION lib
|
LIBRARY DESTINATION lib
|
||||||
ARCHIVE DESTINATION lib)
|
ARCHIVE DESTINATION lib
|
||||||
|
|
||||||
install(EXPORT cpp-ipc-targets
|
|
||||||
FILE cpp-ipc-targets.cmake
|
|
||||||
NAMESPACE cpp-ipc::
|
|
||||||
DESTINATION share/cpp-ipc
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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)
|
|
||||||
@ -38,16 +38,16 @@ buffer::buffer(void* p, std::size_t s, destructor_t d)
|
|||||||
: p_(p_->make(p, s, d, nullptr)) {
|
: p_(p_->make(p, s, d, nullptr)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer::buffer(void* p, std::size_t s, destructor_t d, void* mem_to_free)
|
buffer::buffer(void* p, std::size_t s, destructor_t d, void* additional)
|
||||||
: p_(p_->make(p, s, d, mem_to_free)) {
|
: p_(p_->make(p, s, d, additional)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer::buffer(void* p, std::size_t s)
|
buffer::buffer(void* p, std::size_t s)
|
||||||
: buffer(p, s, nullptr) {
|
: buffer(p, s, nullptr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer::buffer(char & c)
|
buffer::buffer(char const & c)
|
||||||
: buffer(&c, 1) {
|
: buffer(const_cast<char*>(&c), 1) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer::buffer(buffer&& rhs)
|
buffer::buffer(buffer&& rhs)
|
||||||
@ -9,7 +9,6 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
#include "libipc/ipc.h"
|
#include "libipc/ipc.h"
|
||||||
#include "libipc/def.h"
|
#include "libipc/def.h"
|
||||||
@ -18,7 +17,6 @@
|
|||||||
#include "libipc/queue.h"
|
#include "libipc/queue.h"
|
||||||
#include "libipc/policy.h"
|
#include "libipc/policy.h"
|
||||||
#include "libipc/rw_lock.h"
|
#include "libipc/rw_lock.h"
|
||||||
#include "libipc/waiter.h"
|
|
||||||
|
|
||||||
#include "libipc/utility/log.h"
|
#include "libipc/utility/log.h"
|
||||||
#include "libipc/utility/id_pool.h"
|
#include "libipc/utility/id_pool.h"
|
||||||
@ -26,7 +24,10 @@
|
|||||||
#include "libipc/utility/utility.h"
|
#include "libipc/utility/utility.h"
|
||||||
|
|
||||||
#include "libipc/memory/resource.h"
|
#include "libipc/memory/resource.h"
|
||||||
|
|
||||||
#include "libipc/platform/detail.h"
|
#include "libipc/platform/detail.h"
|
||||||
|
#include "libipc/platform/waiter_wrapper.h"
|
||||||
|
|
||||||
#include "libipc/circ/elem_array.h"
|
#include "libipc/circ/elem_array.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -70,23 +71,6 @@ ipc::buff_t make_cache(T& data, std::size_t size) {
|
|||||||
return { ptr, size, ipc::mem::free };
|
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 {
|
struct cache_t {
|
||||||
std::size_t fill_;
|
std::size_t fill_;
|
||||||
ipc::buff_t buff_;
|
ipc::buff_t buff_;
|
||||||
@ -103,70 +87,10 @@ struct cache_t {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct conn_info_head {
|
auto cc_acc() {
|
||||||
|
static ipc::shm::handle acc_h("__CA_CONN__", sizeof(acc_t));
|
||||||
ipc::string prefix_;
|
return static_cast<acc_t*>(acc_h.get());
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
IPC_CONSTEXPR_ std::size_t align_chunk_size(std::size_t size) noexcept {
|
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;
|
return (((size - 1) / ipc::large_msg_align) + 1) * ipc::large_msg_align;
|
||||||
@ -208,32 +132,17 @@ struct chunk_info_t {
|
|||||||
|
|
||||||
auto& chunk_storages() {
|
auto& chunk_storages() {
|
||||||
class chunk_handle_t {
|
class chunk_handle_t {
|
||||||
ipc::unordered_map<ipc::string, ipc::shm::handle> handles_;
|
ipc::shm::handle handle_;
|
||||||
std::mutex lock_;
|
|
||||||
|
|
||||||
static bool make_handle(ipc::shm::handle &h, ipc::string const &shm_name, std::size_t chunk_size) {
|
|
||||||
if (!h.valid() &&
|
|
||||||
!h.acquire( shm_name.c_str(),
|
|
||||||
sizeof(chunk_info_t) + chunk_info_t::chunks_mem_size(chunk_size) )) {
|
|
||||||
ipc::error("[chunk_storages] chunk_shm.id_info_.acquire failed: chunk_size = %zd\n", chunk_size);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
chunk_info_t *get_info(conn_info_head *inf, std::size_t chunk_size) {
|
chunk_info_t *get_info(std::size_t chunk_size) {
|
||||||
ipc::string pref {(inf == nullptr) ? ipc::string{} : inf->prefix_};
|
if (!handle_.valid() &&
|
||||||
ipc::string shm_name {ipc::make_prefix(pref, {"CHUNK_INFO__", ipc::to_string(chunk_size)})};
|
!handle_.acquire( ("__CHUNK_INFO__" + ipc::to_string(chunk_size)).c_str(),
|
||||||
ipc::shm::handle *h;
|
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);
|
||||||
std::lock_guard<std::mutex> guard {lock_};
|
return nullptr;
|
||||||
h = &(handles_[pref]);
|
|
||||||
if (!make_handle(*h, shm_name, 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) {
|
if (info == nullptr) {
|
||||||
ipc::error("[chunk_storages] chunk_shm.id_info_.get failed: chunk_size = %zd\n", chunk_size);
|
ipc::error("[chunk_storages] chunk_shm.id_info_.get failed: chunk_size = %zd\n", chunk_size);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -241,35 +150,29 @@ auto& chunk_storages() {
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
using deleter_t = void (*)(chunk_handle_t*);
|
static ipc::map<std::size_t, chunk_handle_t> chunk_hs;
|
||||||
using chunk_handle_ptr_t = std::unique_ptr<chunk_handle_t, deleter_t>;
|
|
||||||
static ipc::map<std::size_t, chunk_handle_ptr_t> chunk_hs;
|
|
||||||
return chunk_hs;
|
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();
|
auto &storages = chunk_storages();
|
||||||
std::decay_t<decltype(storages)>::iterator it;
|
std::decay_t<decltype(storages)>::iterator it;
|
||||||
{
|
{
|
||||||
static ipc::rw_lock lock;
|
static ipc::rw_lock lock;
|
||||||
IPC_UNUSED_ std::shared_lock<ipc::rw_lock> guard {lock};
|
IPC_UNUSED_ std::shared_lock<ipc::rw_lock> guard {lock};
|
||||||
if ((it = storages.find(chunk_size)) == storages.end()) {
|
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 = std::decay_t<decltype(storages)>::value_type::second_type;
|
||||||
using chunk_handle_t = chunk_handle_ptr_t::element_type;
|
|
||||||
guard.unlock();
|
guard.unlock();
|
||||||
IPC_UNUSED_ std::lock_guard<ipc::rw_lock> guard {lock};
|
IPC_UNUSED_ std::lock_guard<ipc::rw_lock> guard {lock};
|
||||||
it = storages.emplace(chunk_size, chunk_handle_ptr_t{
|
it = storages.emplace(chunk_size, chunk_handle_t{}).first;
|
||||||
ipc::mem::alloc<chunk_handle_t>(), [](chunk_handle_t *p) {
|
|
||||||
ipc::mem::destruct(p);
|
|
||||||
}}).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);
|
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 {};
|
if (info == nullptr) return {};
|
||||||
|
|
||||||
info->lock_.lock();
|
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() };
|
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) {
|
if (id < 0) {
|
||||||
ipc::error("[find_storage] id is invalid: id = %ld, size = %zd\n", (long)id, size);
|
ipc::error("[find_storage] id is invalid: id = %ld, size = %zd\n", (long)id, size);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
std::size_t chunk_size = calc_chunk_size(size);
|
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;
|
if (info == nullptr) return nullptr;
|
||||||
return info->at(chunk_size, id)->data();
|
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) {
|
if (id < 0) {
|
||||||
ipc::error("[release_storage] id is invalid: id = %ld, size = %zd\n", (long)id, size);
|
ipc::error("[release_storage] id is invalid: id = %ld, size = %zd\n", (long)id, size);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::size_t chunk_size = calc_chunk_size(size);
|
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;
|
if (info == nullptr) return;
|
||||||
info->lock_.lock();
|
info->lock_.lock();
|
||||||
info->pool_.release(id);
|
info->pool_.release(id);
|
||||||
@ -328,13 +231,13 @@ bool sub_rc(ipc::wr<Rp, Rc, ipc::trans::broadcast>,
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Flag>
|
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) {
|
if (id < 0) {
|
||||||
ipc::error("[recycle_storage] id is invalid: id = %ld, size = %zd\n", (long)id, size);
|
ipc::error("[recycle_storage] id is invalid: id = %ld, size = %zd\n", (long)id, size);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::size_t chunk_size = calc_chunk_size(size);
|
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;
|
if (info == nullptr) return;
|
||||||
|
|
||||||
auto chunk = info->at(chunk_size, id);
|
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>
|
template <typename MsgT>
|
||||||
bool clear_message(conn_info_head *inf, void* p) {
|
bool clear_message(void* p) {
|
||||||
auto msg = static_cast<MsgT*>(p);
|
auto msg = static_cast<MsgT*>(p);
|
||||||
if (msg->storage_) {
|
if (msg->storage_) {
|
||||||
std::int32_t r_size = static_cast<std::int32_t>(ipc::data_length) + msg->remain_;
|
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);
|
ipc::error("[clear_message] invalid msg size: %d\n", (int)r_size);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
release_storage(*reinterpret_cast<ipc::storage_id_t*>(&msg->data_),
|
release_storage(
|
||||||
inf, static_cast<std::size_t>(r_size));
|
*reinterpret_cast<ipc::storage_id_t*>(&msg->data_),
|
||||||
|
static_cast<std::size_t>(r_size));
|
||||||
}
|
}
|
||||||
return true;
|
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>
|
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();
|
if (tm == 0) return !pred();
|
||||||
for (unsigned k = 0; pred();) {
|
for (unsigned k = 0; pred();) {
|
||||||
bool ret = true;
|
bool loop = true, ret = true;
|
||||||
ipc::sleep(k, [&k, &ret, &waiter, &pred, tm] {
|
ipc::sleep(k, [&k, &loop, &ret, &waiter, &pred, tm] {
|
||||||
ret = waiter.wait_if(std::forward<F>(pred), tm);
|
ret = waiter.wait_if([&loop, &pred] {
|
||||||
k = 0;
|
return loop = pred();
|
||||||
|
}, tm);
|
||||||
|
k = 0;
|
||||||
});
|
});
|
||||||
if (!ret) return false; // timeout or fail
|
if (!ret ) return false; // timeout or fail
|
||||||
if (k == 0) break; // k has been reset
|
if (!loop) break;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -388,32 +326,11 @@ struct queue_generator {
|
|||||||
struct conn_info_t : conn_info_head {
|
struct conn_info_t : conn_info_head {
|
||||||
queue_t que_;
|
queue_t que_;
|
||||||
|
|
||||||
conn_info_t(char const * pref, char const * name)
|
conn_info_t(char const * name)
|
||||||
: conn_info_head{pref, name} { init(); }
|
: conn_info_head{name}
|
||||||
|
, que_{("__QU_CONN__" +
|
||||||
void init() {
|
ipc::to_string(DataSize) + "__" +
|
||||||
conn_info_head::init();
|
ipc::to_string(AlignSize) + "__" + name).c_str()} {
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void disconnect_receiver() {
|
void disconnect_receiver() {
|
||||||
@ -444,18 +361,6 @@ constexpr static queue_t* queue_of(ipc::handle_t h) noexcept {
|
|||||||
|
|
||||||
/* API implementations */
|
/* 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) {
|
static void disconnect(ipc::handle_t h) {
|
||||||
auto que = queue_of(h);
|
auto que = queue_of(h);
|
||||||
if (que == nullptr) {
|
if (que == nullptr) {
|
||||||
@ -473,7 +378,6 @@ static bool reconnect(ipc::handle_t * ph, bool start_to_recv) {
|
|||||||
if (que == nullptr) {
|
if (que == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
info_of(*ph)->init();
|
|
||||||
if (start_to_recv) {
|
if (start_to_recv) {
|
||||||
que->shut_sending();
|
que->shut_sending();
|
||||||
if (que->connect()) { // wouldn't connect twice
|
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();
|
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));
|
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();
|
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);
|
auto que = queue_of(h);
|
||||||
if (que == nullptr) {
|
if (que == nullptr) {
|
||||||
return false;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
// calc a new message id
|
// calc a new message id
|
||||||
conn_info_t *inf = info_of(h);
|
auto acc = info_of(h)->acc();
|
||||||
auto acc = inf->acc();
|
|
||||||
if (acc == nullptr) {
|
if (acc == nullptr) {
|
||||||
ipc::error("fail: send, info_of(h)->acc() == nullptr\n");
|
ipc::error("fail: send, info_of(h)->acc() == nullptr\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto msg_id = acc->fetch_add(1, std::memory_order_relaxed);
|
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) {
|
if (size > ipc::large_msg_limit) {
|
||||||
auto dat = acquire_storage(inf, size, conns);
|
auto dat = acquire_storage(size, conns);
|
||||||
void * buf = dat.second;
|
void * buf = dat.second;
|
||||||
if (buf != nullptr) {
|
if (buf != nullptr) {
|
||||||
std::memcpy(buf, data, size);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool send(ipc::handle_t h, void const * data, std::size_t size, std::uint64_t tm) {
|
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 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) {
|
return [tm, info, que, msg_id](std::int32_t remain, void const * data, std::size_t size) {
|
||||||
if (!wait_for(info->wt_waiter_, [&] {
|
if (!wait_for(info->wt_waiter_, [&] {
|
||||||
return !que->push(
|
return !que->push(
|
||||||
@ -585,7 +497,7 @@ static bool send(ipc::handle_t h, void const * data, std::size_t size, std::uint
|
|||||||
}, tm)) {
|
}, tm)) {
|
||||||
ipc::log("force_push: msg_id = %zd, remain = %d, size = %zd\n", msg_id, remain, size);
|
ipc::log("force_push: msg_id = %zd, remain = %d, size = %zd\n", msg_id, remain, size);
|
||||||
if (!que->force_push(
|
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)) {
|
info->cc_id_, msg_id, remain, data, size)) {
|
||||||
return false;
|
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);
|
}, h, data, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
return send([tm](auto *info, auto *que, auto msg_id) {
|
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) {
|
return [tm, info, que, msg_id](std::int32_t remain, void const * data, std::size_t size) {
|
||||||
if (!wait_for(info->wt_waiter_, [&] {
|
if (!wait_for(info->wt_waiter_, [&] {
|
||||||
return !que->push(
|
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);
|
}, 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);
|
auto que = queue_of(h);
|
||||||
if (que == nullptr) {
|
if (que == nullptr) {
|
||||||
ipc::error("fail: recv, queue_of(h) == nullptr\n");
|
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.
|
// hasn't connected yet, just return.
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
conn_info_t *inf = info_of(h);
|
auto& rc = info_of(h)->recv_cache();
|
||||||
auto& rc = inf->recv_cache();
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
// pop a new message
|
// pop a new message
|
||||||
typename queue_t::value_t msg {};
|
typename queue_t::value_t msg;
|
||||||
if (!wait_for(inf->rd_waiter_, [que, &msg, &h] {
|
if (!wait_for(info_of(h)->rd_waiter_, [que, &msg] {
|
||||||
if (!que->connected()) {
|
|
||||||
reconnect(&h, true);
|
|
||||||
}
|
|
||||||
return !que->pop(msg);
|
return !que->pop(msg);
|
||||||
}, tm)) {
|
}, tm)) {
|
||||||
// pop failed, just return.
|
// pop failed, just return.
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
inf->wt_waiter_.broadcast();
|
info_of(h)->wt_waiter_.broadcast();
|
||||||
if ((inf->acc() != nullptr) && (msg.cc_id_ == inf->cc_id_)) {
|
if ((info_of(h)->acc() != nullptr) && (msg.cc_id_ == info_of(h)->cc_id_)) {
|
||||||
continue; // ignore message to self
|
continue; // ignore message to self
|
||||||
}
|
}
|
||||||
// msg.remain_ may minus & abs(msg.remain_) < data_length
|
// 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
|
// large message
|
||||||
if (msg.storage_) {
|
if (msg.storage_) {
|
||||||
ipc::storage_id_t buf_id = *reinterpret_cast<ipc::storage_id_t*>(&msg.data_);
|
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) {
|
if (buf != nullptr) {
|
||||||
struct recycle_t {
|
struct recycle_t {
|
||||||
ipc::storage_id_t storage_id;
|
ipc::storage_id_t storage_id;
|
||||||
conn_info_t * inf;
|
|
||||||
ipc::circ::cc_t curr_conns;
|
ipc::circ::cc_t curr_conns;
|
||||||
ipc::circ::cc_t conn_id;
|
ipc::circ::cc_t conn_id;
|
||||||
} *r_info = ipc::mem::alloc<recycle_t>(recycle_t{
|
} *r_info = ipc::mem::alloc<recycle_t>(recycle_t{
|
||||||
buf_id,
|
buf_id, que->elems()->connections(std::memory_order_relaxed), que->connected_id()
|
||||||
inf,
|
|
||||||
que->elems()->connections(std::memory_order_relaxed),
|
|
||||||
que->connected_id()
|
|
||||||
});
|
});
|
||||||
if (r_info == nullptr) {
|
if (r_info == nullptr) {
|
||||||
ipc::log("fail: ipc::mem::alloc<recycle_t>.\n");
|
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_UNUSED_ auto finally = ipc::guard([r_info] {
|
||||||
ipc::mem::free(r_info);
|
ipc::mem::free(r_info);
|
||||||
});
|
});
|
||||||
recycle_storage<flag_t>(r_info->storage_id,
|
recycle_storage<flag_t>(r_info->storage_id, size, r_info->curr_conns, r_info->conn_id);
|
||||||
r_info->inf,
|
|
||||||
size,
|
|
||||||
r_info->curr_conns,
|
|
||||||
r_info->conn_id);
|
|
||||||
}, r_info};
|
}, r_info};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -734,22 +634,11 @@ using policy_t = ipc::policy::choose<ipc::circ::elem_array, Flag>;
|
|||||||
|
|
||||||
namespace ipc {
|
namespace ipc {
|
||||||
|
|
||||||
template <typename Flag>
|
|
||||||
ipc::handle_t chan_impl<Flag>::init_first() {
|
|
||||||
ipc::detail::waiter::init();
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Flag>
|
template <typename Flag>
|
||||||
bool chan_impl<Flag>::connect(ipc::handle_t * ph, char const * name, unsigned mode) {
|
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);
|
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>
|
template <typename Flag>
|
||||||
bool chan_impl<Flag>::reconnect(ipc::handle_t * ph, unsigned mode) {
|
bool chan_impl<Flag>::reconnect(ipc::handle_t * ph, unsigned mode) {
|
||||||
return detail_impl<policy_t<Flag>>::reconnect(ph, mode & receiver);
|
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>
|
template <typename Flag>
|
||||||
void chan_impl<Flag>::destroy(ipc::handle_t h) {
|
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);
|
detail_impl<policy_t<Flag>>::destroy(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Flag>
|
template <typename Flag>
|
||||||
char const * chan_impl<Flag>::name(ipc::handle_t h) {
|
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();
|
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>
|
template <typename Flag>
|
||||||
std::size_t chan_impl<Flag>::recv_count(ipc::handle_t h) {
|
std::size_t chan_impl<Flag>::recv_count(ipc::handle_t h) {
|
||||||
return detail_impl<policy_t<Flag>>::recv_count(h);
|
return detail_impl<policy_t<Flag>>::recv_count(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Flag>
|
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);
|
return detail_impl<policy_t<Flag>>::wait_for_recv(h, r_count, tm);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Flag>
|
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);
|
return detail_impl<policy_t<Flag>>::send(h, data, size, tm);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Flag>
|
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);
|
return detail_impl<policy_t<Flag>>::recv(h, tm);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Flag>
|
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);
|
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::single, trans::unicast >>;
|
||||||
// template struct chan_impl<ipc::wr<relat::single, 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 >>; // TBD
|
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::single, relat::multi , trans::broadcast>>;
|
||||||
template struct chan_impl<ipc::wr<relat::multi , relat::multi , trans::broadcast>>;
|
template struct chan_impl<ipc::wr<relat::multi , relat::multi , trans::broadcast>>;
|
||||||
|
|
||||||
@ -37,8 +37,8 @@ private:
|
|||||||
elem_t block_[elem_max] {};
|
elem_t block_[elem_max] {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \remarks 'warning C4348: redefinition of default parameter' with MSVC.
|
* @remarks 'warning C4348: redefinition of default parameter' with MSVC.
|
||||||
* \see
|
* @see
|
||||||
* - https://stackoverflow.com/questions/12656239/redefinition-of-default-template-parameter
|
* - 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
|
* - https://developercommunity.visualstudio.com/content/problem/425978/incorrect-c4348-warning-in-nested-template-declara.html
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -60,7 +60,7 @@ public:
|
|||||||
for (unsigned k = 0;; ipc::yield(k)) {
|
for (unsigned k = 0;; ipc::yield(k)) {
|
||||||
cc_t curr = this->cc_.load(std::memory_order_acquire);
|
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.
|
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.
|
// connection-slot is full.
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -74,10 +74,6 @@ public:
|
|||||||
return this->cc_.fetch_and(~cc_id, std::memory_order_acq_rel) & ~cc_id;
|
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 {
|
std::size_t conn_count(std::memory_order order = std::memory_order_acquire) const noexcept {
|
||||||
cc_t cur = this->cc_.load(order);
|
cc_t cur = this->cc_.load(order);
|
||||||
cc_t cnt; // accumulates the total bits set in cc
|
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 {
|
std::size_t conn_count(std::memory_order order = std::memory_order_acquire) const noexcept {
|
||||||
return this->connections(order);
|
return this->connections(order);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,17 +19,17 @@ namespace mem {
|
|||||||
|
|
||||||
class static_alloc {
|
class static_alloc {
|
||||||
public:
|
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;
|
return size ? std::malloc(size) : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void free(void* p) noexcept {
|
static void free(void* p) {
|
||||||
std::free(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);
|
free(p);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -45,17 +45,14 @@ constexpr char const * pf(long double) { return "%Lf" ; }
|
|||||||
|
|
||||||
} // internal-linkage
|
} // internal-linkage
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct hash : public std::hash<T> {};
|
|
||||||
|
|
||||||
template <typename Key, typename T>
|
template <typename Key, typename T>
|
||||||
using unordered_map = std::unordered_map<
|
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>
|
template <typename Key, typename T>
|
||||||
using map = std::map<
|
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>
|
template <typename Char>
|
||||||
@ -66,18 +63,6 @@ using basic_string = std::basic_string<
|
|||||||
using string = basic_string<char>;
|
using string = basic_string<char>;
|
||||||
using wstring = basic_string<wchar_t>;
|
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>
|
template <typename T>
|
||||||
ipc::string to_string(T val) {
|
ipc::string to_string(T val) {
|
||||||
char buf[std::numeric_limits<T>::digits10 + 1] {};
|
char buf[std::numeric_limits<T>::digits10 + 1] {};
|
||||||
@ -87,24 +72,4 @@ ipc::string to_string(T val) {
|
|||||||
return {};
|
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
|
} // namespace ipc
|
||||||
|
|||||||
@ -1,24 +1,4 @@
|
|||||||
#ifndef LIBIPC_SRC_PLATFORM_DETAIL_H_
|
#pragma once
|
||||||
#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)
|
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@ -72,9 +52,15 @@
|
|||||||
|
|
||||||
#if __cplusplus >= 201703L
|
#if __cplusplus >= 201703L
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
|
||||||
// C++17 and later: std library already provides deduction guides
|
// deduction guides for std::unique_ptr
|
||||||
// No need to add custom ones, just use the standard ones directly
|
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 ipc {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
@ -125,8 +111,17 @@ constexpr const T& (min)(const T& a, const T& b) {
|
|||||||
|
|
||||||
#endif/*__cplusplus < 201703L*/
|
#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 detail
|
||||||
} // namespace ipc
|
} // namespace ipc
|
||||||
|
|
||||||
#endif // defined(__cplusplus)
|
|
||||||
#endif // LIBIPC_SRC_PLATFORM_DETAIL_H_
|
|
||||||
|
|||||||
@ -1,24 +0,0 @@
|
|||||||
This is free and unencumbered software released into the public domain.
|
|
||||||
|
|
||||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
||||||
distribute this software, either in source code form or as a compiled
|
|
||||||
binary, for any purpose, commercial or non-commercial, and by any
|
|
||||||
means.
|
|
||||||
|
|
||||||
In jurisdictions that recognize copyright laws, the author or authors
|
|
||||||
of this software dedicate any and all copyright interest in the
|
|
||||||
software to the public domain. We make this dedication for the benefit
|
|
||||||
of the public at large and to the detriment of our heirs and
|
|
||||||
successors. We intend this dedication to be an overt act of
|
|
||||||
relinquishment in perpetuity of all present and future rights to this
|
|
||||||
software under copyright law.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
||||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
||||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
||||||
OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
For more information, please refer to <http://unlicense.org>
|
|
||||||
@ -1,213 +0,0 @@
|
|||||||
<h1 align="center">
|
|
||||||
<br>
|
|
||||||
<img src="https://raw.githubusercontent.com/alephzero/logo/master/rendered/alephzero.svg" width="256px">
|
|
||||||
<br>
|
|
||||||
AlephZero
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<h3 align="center">Simple, Robust, Fast IPC.</h3>
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://github.com/alephzero/alephzero/actions?query=workflow%3ACI"><img src="https://github.com/alephzero/alephzero/workflows/CI/badge.svg"></a>
|
|
||||||
<a href="https://codecov.io/gh/alephzero/alephzero"><img src="https://codecov.io/gh/alephzero/alephzero/branch/master/graph/badge.svg"></a>
|
|
||||||
<a href="https://alephzero.readthedocs.io/en/latest/?badge=latest"><img src="https://readthedocs.org/projects/alephzero/badge/?version=latest"></a>
|
|
||||||
<a href="http://unlicense.org"><img src="https://img.shields.io/badge/license-Unlicense-blue.svg"></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<a href="#overview">Overview</a> •
|
|
||||||
<a href="#transport">Transport</a> •
|
|
||||||
<a href="#protocol">Protocol</a> •
|
|
||||||
<a href="#examples">Examples</a> •
|
|
||||||
<a href="#installation">Installation</a> •
|
|
||||||
<a href="#across-dockers">Across Dockers</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
# Overview
|
|
||||||
|
|
||||||
[Presentation from March 25, 2020](https://docs.google.com/presentation/d/12KE9UucjZPtpVnM1NljxOqBolBBKECWJdrCoE2yJaBw/edit#slide=id.p)
|
|
||||||
|
|
||||||
AlephZero is a library for message based communication between programs running on the same machine.
|
|
||||||
|
|
||||||
## Simple
|
|
||||||
|
|
||||||
AlephZero's main goal is to be simple to use. Nothing is higher priority.
|
|
||||||
|
|
||||||
There is no "master" process in between your nodes that is needed to do handshakes or exchanges of any kind. All you need is the topic name.
|
|
||||||
|
|
||||||
See the <a href="#examples">Examples</a>.
|
|
||||||
|
|
||||||
## Robust
|
|
||||||
|
|
||||||
This is probably the main value of AlephZero, above similar libraries.
|
|
||||||
|
|
||||||
AlephZero uses a lot of tricks to ensure the state of all channels is consistent, even when programs die. This includes double-buffering the state of the communication channel and [robustifying](https://man7.org/linux/man-pages/man3/pthread_mutexattr_setrobust.3.html) the locks and notification channels.
|
|
||||||
|
|
||||||
## Fast
|
|
||||||
|
|
||||||
AlephZero uses shared memory across multiple processes to read and write messages, minimizing the involvement of the kernel. The kernel only really gets involved in notifying a process that a new message exists, and for that we use futex (fast user-space mutex).
|
|
||||||
|
|
||||||
TODO: Benchmarks
|
|
||||||
|
|
||||||
# Transport
|
|
||||||
|
|
||||||
AlephZero, at its core, is a simple allocator on top of a contiguous region of memory. Usually, shared-memory. The allocator of choice is a circular-linked-list, which is fast, simple, and sufficient for the protocol listed below. It also plays well with the robustness requirement.
|
|
||||||
|
|
||||||
This has a number of implications. For one, this means that old messages are kept around until the space is needed. The oldest messages are always discarded before any more recent messages.
|
|
||||||
|
|
||||||
# Protocol
|
|
||||||
|
|
||||||
Rather than exposing the low-level transport directly, AlephZero provides a few higher level protocol:
|
|
||||||
|
|
||||||
* <b>PubSub</b>: Broadcast published messages. Subscribers get notified.
|
|
||||||
* <b>RPC</b>: Request-response.
|
|
||||||
* <b>PRPC (Progressive RPC)</b>: Request-streaming response.
|
|
||||||
* <b>Sessions</b>: Bi-directional channel of communication. Not yet implemented. Let me know if you want this.
|
|
||||||
|
|
||||||
# Examples
|
|
||||||
|
|
||||||
Many more example and an interactive experience can be found at: https://github.com/alephzero/playground
|
|
||||||
|
|
||||||
For the curious, here are some simple snippets to get you started:
|
|
||||||
|
|
||||||
To begin with, we need to include AlephZero:
|
|
||||||
```cc
|
|
||||||
#include <a0.h>
|
|
||||||
```
|
|
||||||
|
|
||||||
## PubSub
|
|
||||||
|
|
||||||
You can have as many publisher and subscribers on the same topic as you wish. They just need to agree on the filename.
|
|
||||||
|
|
||||||
```cc
|
|
||||||
a0::Publisher p("my_pubsub_topic");
|
|
||||||
p.pub("foo");
|
|
||||||
```
|
|
||||||
|
|
||||||
You just published `"foo"` to the `"my_pubsub_topic"`.
|
|
||||||
|
|
||||||
To read those message, you can create a subscriber on the same topic:
|
|
||||||
```cc
|
|
||||||
a0::Subscriber sub(
|
|
||||||
"my_pubsub_topic",
|
|
||||||
A0_INIT_AWAIT_NEW, // or MOST_RECENT or OLDEST
|
|
||||||
A0_ITER_NEWEST, // or NEXT
|
|
||||||
[](a0::PacketView pkt_view) {
|
|
||||||
std::cout << "Got: " << pkt_view.payload() << std::endl;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
The callback will trigger whenever a message is published.
|
|
||||||
|
|
||||||
The `Subscriber` object spawns a thread that will read the topic and call the callback.
|
|
||||||
|
|
||||||
The `A0_INIT` tells the subscriber where to start reading.
|
|
||||||
* `A0_INIT_AWAIT_NEW`: Start with messages published after the creation of the subscriber.
|
|
||||||
* `A0_INIT_MOST_RECENT`: Start with the most recently published message. Useful for state and configuration. But be careful, this can be quite old!
|
|
||||||
* `A0_INIT_OLDEST`: Topics keep a history of 16MB (unless configures otherwise). Start with the oldest thing still in there.
|
|
||||||
|
|
||||||
The `A0_ITER` tells the subscriber how to continue reading messages. After each callback:
|
|
||||||
* `A0_ITER_NEXT`: grab the sequentially next message. When you don't want to miss a thing.
|
|
||||||
* `A0_ITER_NEWEST`: grab the newest available unread message. When you want to keep up with the firehose.
|
|
||||||
|
|
||||||
```cc
|
|
||||||
a0::SubscriberSync sub_sync(
|
|
||||||
"my_pubsub_topic",
|
|
||||||
A0_INIT_OLDEST, A0_ITER_NEXT);
|
|
||||||
while (sub_sync.has_next()) {
|
|
||||||
auto pkt = sub_sync.next();
|
|
||||||
std::cout << "Got: " << pkt.payload() << std::endl;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## RPC
|
|
||||||
|
|
||||||
Create an `RpcServer`:
|
|
||||||
|
|
||||||
```cc
|
|
||||||
a0::RpcServer server(
|
|
||||||
"my_rpc_topic",
|
|
||||||
/* onrequest = */ [](a0::RpcRequest req) {
|
|
||||||
std::cout << "Got: " << req.pkt().payload() << std::endl;
|
|
||||||
req.reply("echo " + std::string(req.pkt().payload()));
|
|
||||||
},
|
|
||||||
/* oncancel = */ nullptr);
|
|
||||||
```
|
|
||||||
|
|
||||||
Create an `RpcClient`:
|
|
||||||
|
|
||||||
```cc
|
|
||||||
a0::RpcClient client("my_rpc_topic");
|
|
||||||
client.send("client msg", [](a0::PacketView reply) {
|
|
||||||
std::cout << "Got: " << reply.payload() << std::endl;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
|
|
||||||
## Install From Source
|
|
||||||
|
|
||||||
### Ubuntu Dependencies
|
|
||||||
|
|
||||||
```sh
|
|
||||||
apt install g++ make
|
|
||||||
```
|
|
||||||
|
|
||||||
### Alpine Dependencies
|
|
||||||
|
|
||||||
```sh
|
|
||||||
apk add g++ linux-headers make
|
|
||||||
```
|
|
||||||
|
|
||||||
### Download And Install
|
|
||||||
|
|
||||||
```sh
|
|
||||||
git clone https://github.com/alephzero/alephzero.git
|
|
||||||
cd alephzero
|
|
||||||
make install -j
|
|
||||||
```
|
|
||||||
|
|
||||||
## Install From Package
|
|
||||||
|
|
||||||
Coming soon-ish. Let me know if you want this and I'll prioritize it. External support is much appreciated.
|
|
||||||
|
|
||||||
## Integration
|
|
||||||
|
|
||||||
### Command Line
|
|
||||||
|
|
||||||
Add the following to g++ / clang commands.
|
|
||||||
```sh
|
|
||||||
-L${libdir} -lalephzero -lpthread
|
|
||||||
```
|
|
||||||
|
|
||||||
### Package-cfg
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pkg-config --cflags --libs alephzero
|
|
||||||
```
|
|
||||||
|
|
||||||
### CMake
|
|
||||||
|
|
||||||
Coming soon-ish. Let me know if you want this and I'll prioritize it. External support is much appreciated.
|
|
||||||
|
|
||||||
### Bazel
|
|
||||||
|
|
||||||
Coming soon-ish. Let me know if you want this and I'll prioritize it.
|
|
||||||
|
|
||||||
# Across Dockers
|
|
||||||
|
|
||||||
For programs running across different dockers to be able to communicate, we need to have them match up on two flags: `--ipc` and `--pid`.
|
|
||||||
|
|
||||||
* `--ipc` shares the `/dev/shm` filesystem. This is necessary to open the same file topics.
|
|
||||||
* `--pid` shares the process id namespace. This is necessary for the locking and notification systems.
|
|
||||||
|
|
||||||
In the simplest case, you can set them both to `host` and talk through the system's global `/dev/shm` and process id namespace.
|
|
||||||
```sh
|
|
||||||
docker run --ipc=host --pid=host --name=foo foo_image
|
|
||||||
docker run --ipc=host --pid=host --name=bar bar_image
|
|
||||||
```
|
|
||||||
|
|
||||||
Or, you can mark one as `shareable` and have the others connect to it:
|
|
||||||
```sh
|
|
||||||
docker run --ipc=shareable --pid=shareable --name=foo foo_image
|
|
||||||
docker run --ipc=container:foo --pid=container:foo --name=bar bar_image
|
|
||||||
```
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
#ifndef A0_SRC_ATOMIC_H
|
|
||||||
#define A0_SRC_ATOMIC_H
|
|
||||||
|
|
||||||
#include "a0/inline.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
void a0_barrier() {
|
|
||||||
// 'atomic_thread_fence' is not supported with ‘-fsanitize=thread’
|
|
||||||
__sync_synchronize();
|
|
||||||
}
|
|
||||||
|
|
||||||
#define a0_atomic_fetch_add(P, V) __atomic_fetch_add((P), (V), __ATOMIC_RELAXED)
|
|
||||||
#define a0_atomic_add_fetch(P, V) __atomic_add_fetch((P), (V), __ATOMIC_RELAXED)
|
|
||||||
|
|
||||||
#define a0_atomic_fetch_and(P, V) __atomic_fetch_and((P), (V), __ATOMIC_RELAXED)
|
|
||||||
#define a0_atomic_and_fetch(P, V) __atomic_and_fetch((P), (V), __ATOMIC_RELAXED)
|
|
||||||
|
|
||||||
#define a0_atomic_fetch_or(P, V) __atomic_fetch_or((P), (V), __ATOMIC_RELAXED)
|
|
||||||
#define a0_atomic_or_fetch(P, V) __atomic_or_fetch((P), (V), __ATOMIC_RELAXED)
|
|
||||||
|
|
||||||
#define a0_atomic_load(P) __atomic_load_n((P), __ATOMIC_RELAXED)
|
|
||||||
#define a0_atomic_store(P, V) __atomic_store_n((P), (V), __ATOMIC_RELAXED)
|
|
||||||
|
|
||||||
// TODO(lshamis): Switch from __sync to __atomic.
|
|
||||||
#define a0_cas_val(P, OV, NV) __sync_val_compare_and_swap((P), (OV), (NV))
|
|
||||||
#define a0_cas(P, OV, NV) __sync_bool_compare_and_swap((P), (OV), (NV))
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // A0_SRC_ATOMIC_H
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
#ifndef A0_SRC_CLOCK_H
|
|
||||||
#define A0_SRC_CLOCK_H
|
|
||||||
|
|
||||||
#include "a0/err.h"
|
|
||||||
#include "a0/inline.h"
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
#include "err_macro.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static const int64_t NS_PER_SEC = 1e9;
|
|
||||||
|
|
||||||
typedef struct timespec timespec_t;
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
a0_err_t a0_clock_now(clockid_t clk, timespec_t* out) {
|
|
||||||
A0_RETURN_SYSERR_ON_MINUS_ONE(clock_gettime(clk, out));
|
|
||||||
return A0_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
a0_err_t a0_clock_add(timespec_t ts, int64_t add_nsec, timespec_t* out) {
|
|
||||||
out->tv_sec = ts.tv_sec + add_nsec / NS_PER_SEC;
|
|
||||||
out->tv_nsec = ts.tv_nsec + add_nsec % NS_PER_SEC;
|
|
||||||
if (out->tv_nsec >= NS_PER_SEC) {
|
|
||||||
out->tv_sec++;
|
|
||||||
out->tv_nsec -= NS_PER_SEC;
|
|
||||||
} else if (out->tv_nsec < 0) {
|
|
||||||
out->tv_sec--;
|
|
||||||
out->tv_nsec += NS_PER_SEC;
|
|
||||||
}
|
|
||||||
|
|
||||||
return A0_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
a0_err_t a0_clock_convert(
|
|
||||||
clockid_t orig_clk,
|
|
||||||
timespec_t orig_ts,
|
|
||||||
clockid_t target_clk,
|
|
||||||
timespec_t* target_ts) {
|
|
||||||
timespec_t orig_now;
|
|
||||||
A0_RETURN_ERR_ON_ERR(a0_clock_now(orig_clk, &orig_now));
|
|
||||||
timespec_t target_now;
|
|
||||||
A0_RETURN_ERR_ON_ERR(a0_clock_now(target_clk, &target_now));
|
|
||||||
|
|
||||||
int64_t add_nsec = (orig_ts.tv_sec - orig_now.tv_sec) * NS_PER_SEC + (orig_ts.tv_nsec - orig_now.tv_nsec);
|
|
||||||
return a0_clock_add(target_now, add_nsec, target_ts);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // A0_SRC_CLOCK_H
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
#ifndef A0_EMPTY_H
|
|
||||||
#define A0_EMPTY_H
|
|
||||||
|
|
||||||
// Bah. Why is there no consistent way to zero initialize a struct?
|
|
||||||
#ifdef __cplusplus
|
|
||||||
#define A0_EMPTY \
|
|
||||||
{}
|
|
||||||
#else
|
|
||||||
#define A0_EMPTY \
|
|
||||||
{ 0 }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // A0_EMPTY_H
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
#include "a0/err.h"
|
|
||||||
#include "a0/thread_local.h"
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
A0_THREAD_LOCAL int a0_err_syscode;
|
|
||||||
A0_THREAD_LOCAL char a0_err_msg[1024];
|
|
||||||
|
|
||||||
const char* a0_strerror(a0_err_t err) {
|
|
||||||
switch (err) {
|
|
||||||
case A0_OK: {
|
|
||||||
return strerror(0);
|
|
||||||
}
|
|
||||||
case A0_ERR_SYS: {
|
|
||||||
return strerror(a0_err_syscode);
|
|
||||||
}
|
|
||||||
case A0_ERR_CUSTOM_MSG: {
|
|
||||||
return a0_err_msg;
|
|
||||||
}
|
|
||||||
case A0_ERR_INVALID_ARG: {
|
|
||||||
return strerror(EINVAL);
|
|
||||||
}
|
|
||||||
case A0_ERR_RANGE: {
|
|
||||||
return "Index out of bounds";
|
|
||||||
}
|
|
||||||
case A0_ERR_AGAIN: {
|
|
||||||
return "Not available yet";
|
|
||||||
}
|
|
||||||
case A0_ERR_FRAME_LARGE: {
|
|
||||||
return "Frame size too large";
|
|
||||||
}
|
|
||||||
case A0_ERR_ITER_DONE: {
|
|
||||||
return "Done iterating";
|
|
||||||
}
|
|
||||||
case A0_ERR_NOT_FOUND: {
|
|
||||||
return "Not found";
|
|
||||||
}
|
|
||||||
case A0_ERR_BAD_PATH: {
|
|
||||||
return "Invalid path";
|
|
||||||
}
|
|
||||||
case A0_ERR_BAD_TOPIC: {
|
|
||||||
return "Invalid topic name";
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
#ifndef A0_ERR_H
|
|
||||||
#define A0_ERR_H
|
|
||||||
|
|
||||||
#include "a0/thread_local.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef enum a0_err_e {
|
|
||||||
A0_OK = 0,
|
|
||||||
A0_ERR_SYS = 1,
|
|
||||||
A0_ERR_CUSTOM_MSG = 2,
|
|
||||||
A0_ERR_INVALID_ARG = 3,
|
|
||||||
A0_ERR_RANGE = 4,
|
|
||||||
A0_ERR_AGAIN = 5,
|
|
||||||
A0_ERR_ITER_DONE = 6,
|
|
||||||
A0_ERR_NOT_FOUND = 7,
|
|
||||||
A0_ERR_FRAME_LARGE = 8,
|
|
||||||
A0_ERR_BAD_PATH = 9,
|
|
||||||
A0_ERR_BAD_TOPIC = 10,
|
|
||||||
} a0_err_t;
|
|
||||||
|
|
||||||
extern A0_THREAD_LOCAL int a0_err_syscode;
|
|
||||||
extern A0_THREAD_LOCAL char a0_err_msg[1024];
|
|
||||||
|
|
||||||
const char* a0_strerror(a0_err_t);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // A0_ERR_H
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
#ifndef A0_SRC_ERR_MACRO_H
|
|
||||||
#define A0_SRC_ERR_MACRO_H
|
|
||||||
|
|
||||||
#include "a0/err.h"
|
|
||||||
#include "a0/inline.h"
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
a0_err_t A0_MAKE_SYSERR(int syserr) {
|
|
||||||
a0_err_syscode = syserr;
|
|
||||||
return A0_ERR_SYS;
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
int A0_SYSERR(a0_err_t err) {
|
|
||||||
return err == A0_ERR_SYS ? a0_err_syscode : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE_RECURSIVE
|
|
||||||
a0_err_t A0_MAKE_MSGERR(const char* fmt, ...) {
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
if (fmt) {
|
|
||||||
vsnprintf(a0_err_msg, sizeof(a0_err_msg), fmt, args); // NOLINT(clang-analyzer-valist.Uninitialized): https://bugs.llvm.org/show_bug.cgi?id=41311
|
|
||||||
va_end(args);
|
|
||||||
return A0_ERR_CUSTOM_MSG;
|
|
||||||
}
|
|
||||||
va_end(args);
|
|
||||||
return A0_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define A0_RETURN_SYSERR_ON_MINUS_ONE(X) \
|
|
||||||
do { \
|
|
||||||
if ((X) == -1) { \
|
|
||||||
a0_err_syscode = errno; \
|
|
||||||
return A0_ERR_SYS; \
|
|
||||||
} \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#define A0_RETURN_ERR_ON_ERR(X) \
|
|
||||||
do { \
|
|
||||||
a0_err_t _err = (X); \
|
|
||||||
if (_err) { \
|
|
||||||
return _err; \
|
|
||||||
} \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#endif // A0_SRC_ERR_MACRO_H
|
|
||||||
@ -1,111 +0,0 @@
|
|||||||
#ifndef A0_SRC_FTX_H
|
|
||||||
#define A0_SRC_FTX_H
|
|
||||||
|
|
||||||
#include "a0/err.h"
|
|
||||||
#include "a0/inline.h"
|
|
||||||
#include "a0/time.h"
|
|
||||||
|
|
||||||
#include <limits.h>
|
|
||||||
#include <linux/futex.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <syscall.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "clock.h"
|
|
||||||
#include "err_macro.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// FUTEX_WAIT and FUTEX_WAIT_REQUEUE_PI default to CLOCK_MONOTONIC,
|
|
||||||
// but FUTEX_LOCK_PI always uses CLOCK_REALTIME.
|
|
||||||
//
|
|
||||||
// Until someone tells me otherwise, I assume this is bad decision making
|
|
||||||
// and I will instead standardize all things on CLOCK_BOOTTIME.
|
|
||||||
|
|
||||||
// Futex.
|
|
||||||
// Operations rely on the address.
|
|
||||||
// It should not be copied or moved.
|
|
||||||
typedef uint32_t a0_ftx_t;
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
a0_err_t a0_futex(a0_ftx_t* uaddr,
|
|
||||||
int futex_op,
|
|
||||||
int val,
|
|
||||||
uintptr_t timeout_or_val2,
|
|
||||||
a0_ftx_t* uaddr2,
|
|
||||||
int val3) {
|
|
||||||
A0_RETURN_SYSERR_ON_MINUS_ONE(syscall(SYS_futex, uaddr, futex_op, val, timeout_or_val2, uaddr2, val3));
|
|
||||||
return A0_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
a0_err_t a0_ftx_wait(a0_ftx_t* ftx, int confirm_val, const a0_time_mono_t* time_mono) {
|
|
||||||
if (!time_mono) {
|
|
||||||
return a0_futex(ftx, FUTEX_WAIT, confirm_val, 0, NULL, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
timespec_t ts_mono;
|
|
||||||
A0_RETURN_ERR_ON_ERR(a0_clock_convert(CLOCK_BOOTTIME, time_mono->ts, CLOCK_MONOTONIC, &ts_mono));
|
|
||||||
return a0_futex(ftx, FUTEX_WAIT, confirm_val, (uintptr_t)&ts_mono, NULL, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
a0_err_t a0_ftx_wake(a0_ftx_t* ftx, int cnt) {
|
|
||||||
return a0_futex(ftx, FUTEX_WAKE, cnt, 0, NULL, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
a0_err_t a0_ftx_signal(a0_ftx_t* ftx) {
|
|
||||||
return a0_ftx_wake(ftx, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
a0_err_t a0_ftx_broadcast(a0_ftx_t* ftx) {
|
|
||||||
return a0_ftx_wake(ftx, INT_MAX);
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
a0_err_t a0_ftx_lock_pi(a0_ftx_t* ftx, const a0_time_mono_t* time_mono) {
|
|
||||||
if (!time_mono) {
|
|
||||||
return a0_futex(ftx, FUTEX_LOCK_PI, 0, 0, NULL, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
timespec_t ts_wall;
|
|
||||||
A0_RETURN_ERR_ON_ERR(a0_clock_convert(CLOCK_BOOTTIME, time_mono->ts, CLOCK_REALTIME, &ts_wall));
|
|
||||||
return a0_futex(ftx, FUTEX_LOCK_PI, 0, (uintptr_t)&ts_wall, NULL, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
a0_err_t a0_ftx_trylock_pi(a0_ftx_t* ftx) {
|
|
||||||
return a0_futex(ftx, FUTEX_TRYLOCK_PI, 0, 0, NULL, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
a0_err_t a0_ftx_unlock_pi(a0_ftx_t* ftx) {
|
|
||||||
return a0_futex(ftx, FUTEX_UNLOCK_PI, 0, 0, NULL, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
a0_err_t a0_ftx_cmp_requeue_pi(a0_ftx_t* ftx, int confirm_val, a0_ftx_t* requeue_ftx, int max_requeue) {
|
|
||||||
return a0_futex(ftx, FUTEX_CMP_REQUEUE_PI, 1, max_requeue, requeue_ftx, confirm_val);
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
a0_err_t a0_ftx_wait_requeue_pi(a0_ftx_t* ftx, int confirm_val, const a0_time_mono_t* time_mono, a0_ftx_t* requeue_ftx) {
|
|
||||||
if (!time_mono) {
|
|
||||||
return a0_futex(ftx, FUTEX_WAIT_REQUEUE_PI, confirm_val, 0, requeue_ftx, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
timespec_t ts_mono;
|
|
||||||
A0_RETURN_ERR_ON_ERR(a0_clock_convert(CLOCK_BOOTTIME, time_mono->ts, CLOCK_MONOTONIC, &ts_mono));
|
|
||||||
return a0_futex(ftx, FUTEX_WAIT_REQUEUE_PI, confirm_val, (uintptr_t)&ts_mono, requeue_ftx, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // A0_SRC_FTX_H
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
#ifndef A0_INLINE_H
|
|
||||||
#define A0_INLINE_H
|
|
||||||
|
|
||||||
#define A0_STATIC_INLINE static inline __attribute__((always_inline))
|
|
||||||
|
|
||||||
#define A0_STATIC_INLINE_RECURSIVE static inline
|
|
||||||
|
|
||||||
#endif // A0_INLINE_H
|
|
||||||
@ -1,420 +0,0 @@
|
|||||||
#include "a0/err.h"
|
|
||||||
#include "a0/inline.h"
|
|
||||||
#include "a0/mtx.h"
|
|
||||||
#include "a0/thread_local.h"
|
|
||||||
#include "a0/tid.h"
|
|
||||||
#include "a0/time.h"
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <linux/futex.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <syscall.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "atomic.h"
|
|
||||||
#include "clock.h"
|
|
||||||
#include "err_macro.h"
|
|
||||||
#include "ftx.h"
|
|
||||||
|
|
||||||
// TSAN is worth the pain of properly annotating our mutex.
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
#if defined(__SANITIZE_THREAD__)
|
|
||||||
#define A0_TSAN_ENABLED
|
|
||||||
#elif defined(__has_feature)
|
|
||||||
#if __has_feature(thread_sanitizer)
|
|
||||||
#define A0_TSAN_ENABLED
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
const unsigned __tsan_mutex_linker_init = 1 << 0;
|
|
||||||
const unsigned __tsan_mutex_write_reentrant = 1 << 1;
|
|
||||||
const unsigned __tsan_mutex_read_reentrant = 1 << 2;
|
|
||||||
const unsigned __tsan_mutex_not_static = 1 << 8;
|
|
||||||
const unsigned __tsan_mutex_read_lock = 1 << 3;
|
|
||||||
const unsigned __tsan_mutex_try_lock = 1 << 4;
|
|
||||||
const unsigned __tsan_mutex_try_lock_failed = 1 << 5;
|
|
||||||
const unsigned __tsan_mutex_recursive_lock = 1 << 6;
|
|
||||||
const unsigned __tsan_mutex_recursive_unlock = 1 << 7;
|
|
||||||
|
|
||||||
#ifdef A0_TSAN_ENABLED
|
|
||||||
|
|
||||||
void __tsan_mutex_create(void* addr, unsigned flags);
|
|
||||||
void __tsan_mutex_destroy(void* addr, unsigned flags);
|
|
||||||
void __tsan_mutex_pre_lock(void* addr, unsigned flags);
|
|
||||||
void __tsan_mutex_post_lock(void* addr, unsigned flags, int recursion);
|
|
||||||
int __tsan_mutex_pre_unlock(void* addr, unsigned flags);
|
|
||||||
void __tsan_mutex_post_unlock(void* addr, unsigned flags);
|
|
||||||
void __tsan_mutex_pre_signal(void* addr, unsigned flags);
|
|
||||||
void __tsan_mutex_post_signal(void* addr, unsigned flags);
|
|
||||||
void __tsan_mutex_pre_divert(void* addr, unsigned flags);
|
|
||||||
void __tsan_mutex_post_divert(void* addr, unsigned flags);
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
#define _u_ __attribute__((unused))
|
|
||||||
|
|
||||||
A0_STATIC_INLINE void _u_ __tsan_mutex_create(_u_ void* addr, _u_ unsigned flags) {}
|
|
||||||
A0_STATIC_INLINE void _u_ __tsan_mutex_destroy(_u_ void* addr, _u_ unsigned flags) {}
|
|
||||||
A0_STATIC_INLINE void _u_ __tsan_mutex_pre_lock(_u_ void* addr, _u_ unsigned flags) {}
|
|
||||||
A0_STATIC_INLINE void _u_ __tsan_mutex_post_lock(_u_ void* addr,
|
|
||||||
_u_ unsigned flags,
|
|
||||||
_u_ int recursion) {}
|
|
||||||
A0_STATIC_INLINE int _u_ __tsan_mutex_pre_unlock(_u_ void* addr, _u_ unsigned flags) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
A0_STATIC_INLINE void _u_ __tsan_mutex_post_unlock(_u_ void* addr, _u_ unsigned flags) {}
|
|
||||||
A0_STATIC_INLINE void _u_ __tsan_mutex_pre_signal(_u_ void* addr, _u_ unsigned flags) {}
|
|
||||||
A0_STATIC_INLINE void _u_ __tsan_mutex_post_signal(_u_ void* addr, _u_ unsigned flags) {}
|
|
||||||
A0_STATIC_INLINE void _u_ __tsan_mutex_pre_divert(_u_ void* addr, _u_ unsigned flags) {}
|
|
||||||
A0_STATIC_INLINE void _u_ __tsan_mutex_post_divert(_u_ void* addr, _u_ unsigned flags) {}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
A0_THREAD_LOCAL bool a0_robust_init = false;
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
void a0_robust_reset() {
|
|
||||||
a0_robust_init = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
void a0_robust_reset_atfork() {
|
|
||||||
pthread_atfork(NULL, NULL, &a0_robust_reset);
|
|
||||||
}
|
|
||||||
|
|
||||||
static pthread_once_t a0_robust_reset_atfork_once;
|
|
||||||
|
|
||||||
typedef struct robust_list robust_list_t;
|
|
||||||
typedef struct robust_list_head robust_list_head_t;
|
|
||||||
|
|
||||||
A0_THREAD_LOCAL robust_list_head_t a0_robust_head;
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
void robust_init() {
|
|
||||||
a0_robust_head.list.next = &a0_robust_head.list;
|
|
||||||
a0_robust_head.futex_offset = offsetof(a0_mtx_t, ftx);
|
|
||||||
a0_robust_head.list_op_pending = NULL;
|
|
||||||
syscall(SYS_set_robust_list, &a0_robust_head.list, sizeof(a0_robust_head));
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
void init_thread() {
|
|
||||||
if (a0_robust_init) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pthread_once(&a0_robust_reset_atfork_once, a0_robust_reset_atfork);
|
|
||||||
robust_init();
|
|
||||||
a0_robust_init = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
void robust_op_start(a0_mtx_t* mtx) {
|
|
||||||
init_thread();
|
|
||||||
a0_robust_head.list_op_pending = (struct robust_list*)mtx;
|
|
||||||
a0_barrier();
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
void robust_op_end(a0_mtx_t* mtx) {
|
|
||||||
(void)mtx;
|
|
||||||
a0_barrier();
|
|
||||||
a0_robust_head.list_op_pending = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
bool robust_is_head(a0_mtx_t* mtx) {
|
|
||||||
return mtx == (a0_mtx_t*)&a0_robust_head;
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
void robust_op_add(a0_mtx_t* mtx) {
|
|
||||||
a0_mtx_t* old_first = (a0_mtx_t*)a0_robust_head.list.next;
|
|
||||||
|
|
||||||
mtx->prev = (a0_mtx_t*)&a0_robust_head;
|
|
||||||
mtx->next = old_first;
|
|
||||||
|
|
||||||
a0_barrier();
|
|
||||||
|
|
||||||
a0_robust_head.list.next = (robust_list_t*)mtx;
|
|
||||||
if (!robust_is_head(old_first)) {
|
|
||||||
old_first->prev = mtx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
void robust_op_del(a0_mtx_t* mtx) {
|
|
||||||
a0_mtx_t* prev = mtx->prev;
|
|
||||||
a0_mtx_t* next = mtx->next;
|
|
||||||
prev->next = next;
|
|
||||||
if (!robust_is_head(next)) {
|
|
||||||
next->prev = prev;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
uint32_t ftx_tid(a0_ftx_t ftx) {
|
|
||||||
return ftx & FUTEX_TID_MASK;
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
bool ftx_owner_died(a0_ftx_t ftx) {
|
|
||||||
return ftx & FUTEX_OWNER_DIED;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const uint32_t FTX_NOTRECOVERABLE = FUTEX_TID_MASK | FUTEX_OWNER_DIED;
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
bool ftx_notrecoverable(a0_ftx_t ftx) {
|
|
||||||
return (ftx & FTX_NOTRECOVERABLE) == FTX_NOTRECOVERABLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
a0_err_t a0_mtx_timedlock_robust(a0_mtx_t* mtx, const a0_time_mono_t* timeout) {
|
|
||||||
const uint32_t tid = a0_tid();
|
|
||||||
|
|
||||||
int syserr = EINTR;
|
|
||||||
while (syserr == EINTR) {
|
|
||||||
// Can't lock if borked.
|
|
||||||
if (ftx_notrecoverable(a0_atomic_load(&mtx->ftx))) {
|
|
||||||
return A0_MAKE_SYSERR(ENOTRECOVERABLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to lock without kernel involvement.
|
|
||||||
if (a0_cas(&mtx->ftx, 0, tid)) {
|
|
||||||
return A0_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ask the kernel to lock.
|
|
||||||
syserr = A0_SYSERR(a0_ftx_lock_pi(&mtx->ftx, timeout));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!syserr) {
|
|
||||||
if (ftx_owner_died(a0_atomic_load(&mtx->ftx))) {
|
|
||||||
return A0_MAKE_SYSERR(EOWNERDEAD);
|
|
||||||
}
|
|
||||||
return A0_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
return A0_MAKE_SYSERR(syserr);
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
a0_err_t a0_mtx_timedlock_impl(a0_mtx_t* mtx, const a0_time_mono_t* timeout) {
|
|
||||||
// Note: __tsan_mutex_pre_lock should come here, but tsan doesn't provide
|
|
||||||
// a way to "fail" a lock. Only a trylock.
|
|
||||||
robust_op_start(mtx);
|
|
||||||
const a0_err_t err = a0_mtx_timedlock_robust(mtx, timeout);
|
|
||||||
if (!err || A0_SYSERR(err) == EOWNERDEAD) {
|
|
||||||
__tsan_mutex_pre_lock(mtx, 0);
|
|
||||||
robust_op_add(mtx);
|
|
||||||
__tsan_mutex_post_lock(mtx, 0, 0);
|
|
||||||
}
|
|
||||||
robust_op_end(mtx);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
a0_err_t a0_mtx_timedlock(a0_mtx_t* mtx, a0_time_mono_t timeout) {
|
|
||||||
return a0_mtx_timedlock_impl(mtx, &timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
a0_err_t a0_mtx_lock(a0_mtx_t* mtx) {
|
|
||||||
return a0_mtx_timedlock_impl(mtx, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
a0_err_t a0_mtx_trylock_impl(a0_mtx_t* mtx) {
|
|
||||||
const uint32_t tid = a0_tid();
|
|
||||||
|
|
||||||
// Try to lock without kernel involvement.
|
|
||||||
uint32_t old = a0_cas_val(&mtx->ftx, 0, tid);
|
|
||||||
|
|
||||||
// Did it work?
|
|
||||||
if (!old) {
|
|
||||||
robust_op_add(mtx);
|
|
||||||
return A0_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is the lock still usable?
|
|
||||||
if (ftx_notrecoverable(old)) {
|
|
||||||
return A0_MAKE_SYSERR(ENOTRECOVERABLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is the owner still alive?
|
|
||||||
if (!ftx_owner_died(old)) {
|
|
||||||
return A0_MAKE_SYSERR(EBUSY);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Oh, the owner died. Ask the kernel to fix the state.
|
|
||||||
a0_err_t err = a0_ftx_trylock_pi(&mtx->ftx);
|
|
||||||
if (!err) {
|
|
||||||
robust_op_add(mtx);
|
|
||||||
if (ftx_owner_died(a0_atomic_load(&mtx->ftx))) {
|
|
||||||
return A0_MAKE_SYSERR(EOWNERDEAD);
|
|
||||||
}
|
|
||||||
return A0_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// EAGAIN means that somebody else beat us to it.
|
|
||||||
// Anything else means we're borked.
|
|
||||||
if (A0_SYSERR(err) == EAGAIN) {
|
|
||||||
return A0_MAKE_SYSERR(EBUSY);
|
|
||||||
}
|
|
||||||
return A0_MAKE_SYSERR(ENOTRECOVERABLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
a0_err_t a0_mtx_trylock(a0_mtx_t* mtx) {
|
|
||||||
__tsan_mutex_pre_lock(mtx, __tsan_mutex_try_lock);
|
|
||||||
robust_op_start(mtx);
|
|
||||||
a0_err_t err = a0_mtx_trylock_impl(mtx);
|
|
||||||
robust_op_end(mtx);
|
|
||||||
if (!err || A0_SYSERR(err) == EOWNERDEAD) {
|
|
||||||
__tsan_mutex_post_lock(mtx, __tsan_mutex_try_lock, 0);
|
|
||||||
} else {
|
|
||||||
__tsan_mutex_post_lock(mtx, __tsan_mutex_try_lock | __tsan_mutex_try_lock_failed, 0);
|
|
||||||
}
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
a0_err_t a0_mtx_consistent(a0_mtx_t* mtx) {
|
|
||||||
const uint32_t val = a0_atomic_load(&mtx->ftx);
|
|
||||||
|
|
||||||
// Why fix what isn't broken?
|
|
||||||
if (!ftx_owner_died(val)) {
|
|
||||||
return A0_MAKE_SYSERR(EINVAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is it yours to fix?
|
|
||||||
if (ftx_tid(val) != a0_tid()) {
|
|
||||||
return A0_MAKE_SYSERR(EPERM);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fix it!
|
|
||||||
a0_atomic_and_fetch(&mtx->ftx, ~FUTEX_OWNER_DIED);
|
|
||||||
|
|
||||||
return A0_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
a0_err_t a0_mtx_unlock(a0_mtx_t* mtx) {
|
|
||||||
const uint32_t tid = a0_tid();
|
|
||||||
|
|
||||||
const uint32_t val = a0_atomic_load(&mtx->ftx);
|
|
||||||
|
|
||||||
// Only the owner can unlock.
|
|
||||||
if (ftx_tid(val) != tid) {
|
|
||||||
return A0_MAKE_SYSERR(EPERM);
|
|
||||||
}
|
|
||||||
|
|
||||||
__tsan_mutex_pre_unlock(mtx, 0);
|
|
||||||
|
|
||||||
// If the mutex was acquired with EOWNERDEAD, the caller is responsible
|
|
||||||
// for fixing the state and marking the mutex consistent. If they did
|
|
||||||
// not mark it consistent and are unlocking... then we are unrecoverably
|
|
||||||
// borked!
|
|
||||||
uint32_t new_val = 0;
|
|
||||||
if (ftx_owner_died(val)) {
|
|
||||||
new_val = FTX_NOTRECOVERABLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
robust_op_start(mtx);
|
|
||||||
robust_op_del(mtx);
|
|
||||||
|
|
||||||
// If the futex is exactly equal to tid, then there are no waiters and the
|
|
||||||
// kernel doesn't need to get involved.
|
|
||||||
if (!a0_cas(&mtx->ftx, tid, new_val)) {
|
|
||||||
// Ask the kernel to wake up a waiter.
|
|
||||||
a0_ftx_unlock_pi(&mtx->ftx);
|
|
||||||
if (new_val) {
|
|
||||||
a0_atomic_or_fetch(&mtx->ftx, new_val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
robust_op_end(mtx);
|
|
||||||
__tsan_mutex_post_unlock(mtx, 0);
|
|
||||||
|
|
||||||
return A0_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(lshamis): Handle ENOTRECOVERABLE
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
a0_err_t a0_cnd_timedwait_impl(a0_cnd_t* cnd, a0_mtx_t* mtx, const a0_time_mono_t* timeout) {
|
|
||||||
const uint32_t init_cnd = a0_atomic_load(cnd);
|
|
||||||
|
|
||||||
// Unblock other threads to do the things that will eventually signal this wait.
|
|
||||||
a0_err_t err = a0_mtx_unlock(mtx);
|
|
||||||
if (err) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
__tsan_mutex_pre_lock(mtx, 0);
|
|
||||||
robust_op_start(mtx);
|
|
||||||
|
|
||||||
do {
|
|
||||||
// Priority-inheritance-aware wait until awoken or timeout.
|
|
||||||
err = a0_ftx_wait_requeue_pi(cnd, init_cnd, timeout, &mtx->ftx);
|
|
||||||
} while (A0_SYSERR(err) == EINTR);
|
|
||||||
|
|
||||||
// We need to manually lock on timeout.
|
|
||||||
// Note: We keep the timeout error.
|
|
||||||
if (A0_SYSERR(err) == ETIMEDOUT) {
|
|
||||||
a0_mtx_timedlock_robust(mtx, NULL);
|
|
||||||
}
|
|
||||||
// Someone else grabbed and mutated the resource between the unlock and wait.
|
|
||||||
// No need to wait.
|
|
||||||
if (A0_SYSERR(err) == EAGAIN) {
|
|
||||||
err = a0_mtx_timedlock_robust(mtx, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
robust_op_add(mtx);
|
|
||||||
|
|
||||||
// If no higher priority error, check the previous owner didn't die.
|
|
||||||
if (!err) {
|
|
||||||
err = ftx_owner_died(a0_atomic_load(&mtx->ftx)) ? EOWNERDEAD : A0_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
robust_op_end(mtx);
|
|
||||||
__tsan_mutex_post_lock(mtx, 0, 0);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
a0_err_t a0_cnd_timedwait(a0_cnd_t* cnd, a0_mtx_t* mtx, a0_time_mono_t timeout) {
|
|
||||||
// Let's not unlock the mutex if we're going to get EINVAL due to a bad timeout.
|
|
||||||
if ((timeout.ts.tv_sec < 0 || timeout.ts.tv_nsec < 0 || (!timeout.ts.tv_sec && !timeout.ts.tv_nsec) || timeout.ts.tv_nsec >= NS_PER_SEC)) {
|
|
||||||
return A0_MAKE_SYSERR(EINVAL);
|
|
||||||
}
|
|
||||||
return a0_cnd_timedwait_impl(cnd, mtx, &timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
a0_err_t a0_cnd_wait(a0_cnd_t* cnd, a0_mtx_t* mtx) {
|
|
||||||
return a0_cnd_timedwait_impl(cnd, mtx, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
a0_err_t a0_cnd_wake(a0_cnd_t* cnd, a0_mtx_t* mtx, uint32_t cnt) {
|
|
||||||
uint32_t val = a0_atomic_add_fetch(cnd, 1);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
a0_err_t err = a0_ftx_cmp_requeue_pi(cnd, val, &mtx->ftx, cnt);
|
|
||||||
if (A0_SYSERR(err) != EAGAIN) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Another thread is also trying to wake this condition variable.
|
|
||||||
val = a0_atomic_load(cnd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a0_err_t a0_cnd_signal(a0_cnd_t* cnd, a0_mtx_t* mtx) {
|
|
||||||
return a0_cnd_wake(cnd, mtx, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
a0_err_t a0_cnd_broadcast(a0_cnd_t* cnd, a0_mtx_t* mtx) {
|
|
||||||
return a0_cnd_wake(cnd, mtx, INT_MAX);
|
|
||||||
}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
#ifndef A0_MTX_H
|
|
||||||
#define A0_MTX_H
|
|
||||||
|
|
||||||
#include "a0/err.h"
|
|
||||||
#include "a0/time.h"
|
|
||||||
#include "a0/unused.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef uint32_t a0_ftx_t;
|
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/61645966/is-typedef-allowed-before-definition
|
|
||||||
struct a0_mtx_s;
|
|
||||||
|
|
||||||
typedef struct a0_mtx_s a0_mtx_t;
|
|
||||||
|
|
||||||
// Mutex implementation designed for IPC.
|
|
||||||
//
|
|
||||||
// Similar to pthread_mutex_t with the following flags fixed:
|
|
||||||
// * Process shared.
|
|
||||||
// * Robust.
|
|
||||||
// * Error checking.
|
|
||||||
// * Priority inheriting.
|
|
||||||
//
|
|
||||||
// Unlike pthread_mutex_t, timespec are expected to use CLOCK_BOOTTIME.
|
|
||||||
//
|
|
||||||
// struct a0_mtx_s "Inherits" from robust_list, which requires:
|
|
||||||
// * The first field MUST be a next pointer.
|
|
||||||
// * There must be a futex, which makes the mutex immovable.
|
|
||||||
//
|
|
||||||
// Note: a mutex MUST be unlocked before being freed or unmapped.
|
|
||||||
struct a0_mtx_s {
|
|
||||||
a0_mtx_t* next;
|
|
||||||
a0_mtx_t* prev;
|
|
||||||
a0_ftx_t ftx;
|
|
||||||
};
|
|
||||||
|
|
||||||
a0_err_t a0_mtx_lock(a0_mtx_t*) A0_WARN_UNUSED_RESULT;
|
|
||||||
a0_err_t a0_mtx_timedlock(a0_mtx_t*, a0_time_mono_t) A0_WARN_UNUSED_RESULT;
|
|
||||||
a0_err_t a0_mtx_trylock(a0_mtx_t*) A0_WARN_UNUSED_RESULT;
|
|
||||||
a0_err_t a0_mtx_consistent(a0_mtx_t*);
|
|
||||||
a0_err_t a0_mtx_unlock(a0_mtx_t*);
|
|
||||||
|
|
||||||
typedef a0_ftx_t a0_cnd_t;
|
|
||||||
|
|
||||||
a0_err_t a0_cnd_wait(a0_cnd_t*, a0_mtx_t*);
|
|
||||||
a0_err_t a0_cnd_timedwait(a0_cnd_t*, a0_mtx_t*, a0_time_mono_t);
|
|
||||||
a0_err_t a0_cnd_signal(a0_cnd_t*, a0_mtx_t*);
|
|
||||||
a0_err_t a0_cnd_broadcast(a0_cnd_t*, a0_mtx_t*);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // A0_MTX_H
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
#include "strconv.h"
|
|
||||||
|
|
||||||
#include "a0/err.h"
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
static const char DECIMAL_DIGITS[] =
|
|
||||||
"00010203040506070809"
|
|
||||||
"10111213141516171819"
|
|
||||||
"20212223242526272829"
|
|
||||||
"30313233343536373839"
|
|
||||||
"40414243444546474849"
|
|
||||||
"50515253545556575859"
|
|
||||||
"60616263646566676869"
|
|
||||||
"70717273747576777879"
|
|
||||||
"80818283848586878889"
|
|
||||||
"90919293949596979899";
|
|
||||||
|
|
||||||
a0_err_t a0_u32_to_str(uint32_t val, char* buf_start, char* buf_end, char** start_ptr) {
|
|
||||||
return a0_u64_to_str(val, buf_start, buf_end, start_ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
a0_err_t a0_u64_to_str(uint64_t val, char* buf_start, char* buf_end, char** start_ptr) {
|
|
||||||
uint64_t orig_val = val;
|
|
||||||
char* ptr = buf_end;
|
|
||||||
while (val >= 10) {
|
|
||||||
ptr -= 2;
|
|
||||||
memcpy(ptr, &DECIMAL_DIGITS[2 * (val % 100)], sizeof(uint16_t));
|
|
||||||
val /= 100;
|
|
||||||
}
|
|
||||||
if (val) {
|
|
||||||
*--ptr = (char)('0' + val);
|
|
||||||
}
|
|
||||||
memset(buf_start, '0', ptr - buf_start);
|
|
||||||
ptr -= (!orig_val);
|
|
||||||
if (start_ptr) {
|
|
||||||
*start_ptr = ptr;
|
|
||||||
}
|
|
||||||
return A0_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
a0_err_t a0_str_to_u32(const char* start, const char* end, uint32_t* out) {
|
|
||||||
*out = 0;
|
|
||||||
for (const char* ptr = start; ptr < end; ptr++) {
|
|
||||||
if (*ptr < '0' || *ptr > '9') {
|
|
||||||
return A0_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
*out *= 10;
|
|
||||||
*out += *ptr - '0';
|
|
||||||
}
|
|
||||||
return A0_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
a0_err_t a0_str_to_u64(const char* start, const char* end, uint64_t* out) {
|
|
||||||
*out = 0;
|
|
||||||
for (const char* ptr = start; ptr < end; ptr++) {
|
|
||||||
if (*ptr < '0' || *ptr > '9') {
|
|
||||||
return A0_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
*out *= 10;
|
|
||||||
*out += *ptr - '0';
|
|
||||||
}
|
|
||||||
return A0_OK;
|
|
||||||
}
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
#ifndef A0_SRC_STRCONV_H
|
|
||||||
#define A0_SRC_STRCONV_H
|
|
||||||
|
|
||||||
#include "a0/err.h"
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Converts a uint32 or uint64 to string.
|
|
||||||
// The entire buffer will be populated, if not with given the value, then with '0'.
|
|
||||||
// Populates the buffer from the back.
|
|
||||||
// start_ptr, if not null, will be set to the point within the buffer where the number starts.
|
|
||||||
// Does NOT check for overflow.
|
|
||||||
a0_err_t a0_u32_to_str(uint32_t val, char* buf_start, char* buf_end, char** start_ptr);
|
|
||||||
a0_err_t a0_u64_to_str(uint64_t val, char* buf_start, char* buf_end, char** start_ptr);
|
|
||||||
|
|
||||||
// Converts a string to uint32 or uint64.
|
|
||||||
// The string may have leading '0's.
|
|
||||||
// Returns A0_ERR_INVALID_ARG if any character is not a digit.
|
|
||||||
// Does NOT check for overflow.
|
|
||||||
a0_err_t a0_str_to_u32(const char* start, const char* end, uint32_t* out);
|
|
||||||
a0_err_t a0_str_to_u64(const char* start, const char* end, uint64_t* out);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // A0_SRC_STRCONV_H
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
#ifndef A0_THREAD_LOCAL_H
|
|
||||||
#define A0_THREAD_LOCAL_H
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
#define A0_THREAD_LOCAL thread_local
|
|
||||||
#else
|
|
||||||
#define A0_THREAD_LOCAL _Thread_local
|
|
||||||
#endif // __cplusplus
|
|
||||||
|
|
||||||
#endif // A0_THREAD_LOCAL_H
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
#include "a0/inline.h"
|
|
||||||
#include "a0/thread_local.h"
|
|
||||||
#include "a0/tid.h"
|
|
||||||
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <syscall.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
A0_THREAD_LOCAL uint32_t a0_tid_cache = 0;
|
|
||||||
static pthread_once_t a0_tid_reset_atfork_once;
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
void a0_tid_reset() {
|
|
||||||
a0_tid_cache = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
A0_STATIC_INLINE
|
|
||||||
void a0_tid_reset_atfork() {
|
|
||||||
pthread_atfork(NULL, NULL, &a0_tid_reset);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t a0_tid() {
|
|
||||||
if (!a0_tid_cache) {
|
|
||||||
a0_tid_cache = syscall(SYS_gettid);
|
|
||||||
pthread_once(&a0_tid_reset_atfork_once, a0_tid_reset_atfork);
|
|
||||||
}
|
|
||||||
return a0_tid_cache;
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
#ifndef A0_TID_H
|
|
||||||
#define A0_TID_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
uint32_t a0_tid();
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // A0_TID_H
|
|
||||||
@ -1,124 +0,0 @@
|
|||||||
#include "a0/empty.h"
|
|
||||||
#include "a0/err.h"
|
|
||||||
#include "a0/time.h"
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
#include "clock.h"
|
|
||||||
#include "err_macro.h"
|
|
||||||
#include "strconv.h"
|
|
||||||
|
|
||||||
const char A0_TIME_MONO[] = "a0_time_mono";
|
|
||||||
|
|
||||||
a0_err_t a0_time_mono_now(a0_time_mono_t* out) {
|
|
||||||
return a0_clock_now(CLOCK_BOOTTIME, &out->ts);
|
|
||||||
}
|
|
||||||
|
|
||||||
a0_err_t a0_time_mono_str(a0_time_mono_t time_mono, char mono_str[20]) {
|
|
||||||
// Mono time as unsigned integer with up to 20 chars: "18446744072709551615"
|
|
||||||
uint64_t ns = time_mono.ts.tv_sec * NS_PER_SEC + time_mono.ts.tv_nsec;
|
|
||||||
mono_str[19] = '\0';
|
|
||||||
return a0_u64_to_str(ns, mono_str, mono_str + 19, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
a0_err_t a0_time_mono_parse(const char mono_str[20], a0_time_mono_t* out) {
|
|
||||||
uint64_t ns;
|
|
||||||
A0_RETURN_ERR_ON_ERR(a0_str_to_u64(mono_str, mono_str + 19, &ns));
|
|
||||||
out->ts.tv_sec = ns / NS_PER_SEC;
|
|
||||||
out->ts.tv_nsec = ns % NS_PER_SEC;
|
|
||||||
return A0_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
a0_err_t a0_time_mono_add(a0_time_mono_t time_mono, int64_t add_nsec, a0_time_mono_t* out) {
|
|
||||||
return a0_clock_add(time_mono.ts, add_nsec, &out->ts);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char A0_TIME_WALL[] = "a0_time_wall";
|
|
||||||
|
|
||||||
a0_err_t a0_time_wall_now(a0_time_wall_t* out) {
|
|
||||||
A0_RETURN_SYSERR_ON_MINUS_ONE(clock_gettime(CLOCK_REALTIME, &out->ts));
|
|
||||||
return A0_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
a0_err_t a0_time_wall_str(a0_time_wall_t wall_time, char wall_str[36]) {
|
|
||||||
// Wall time in RFC 3999 Nano: "2006-01-02T15:04:05.999999999-07:00"
|
|
||||||
struct tm wall_tm;
|
|
||||||
gmtime_r(&wall_time.ts.tv_sec, &wall_tm);
|
|
||||||
|
|
||||||
strftime(&wall_str[0], 20, "%Y-%m-%dT%H:%M:%S", &wall_tm);
|
|
||||||
snprintf(&wall_str[19], 17, ".%09ld-00:00", wall_time.ts.tv_nsec);
|
|
||||||
wall_str[35] = '\0';
|
|
||||||
|
|
||||||
return A0_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
a0_err_t a0_time_wall_parse(const char wall_str[36], a0_time_wall_t* out) {
|
|
||||||
// strptime requires _GNU_SOURCE, which we don't want, so we do it our selves.
|
|
||||||
// Hard code "%Y-%m-%dT%H:%M:%S" + ".%09ld-00:00" pattern.
|
|
||||||
|
|
||||||
struct tm wall_tm = A0_EMPTY;
|
|
||||||
// %Y
|
|
||||||
A0_RETURN_ERR_ON_ERR(a0_str_to_u32(wall_str + 0, wall_str + 4, (uint32_t*)&wall_tm.tm_year));
|
|
||||||
wall_tm.tm_year -= 1900;
|
|
||||||
// -
|
|
||||||
if (wall_str[4] != '-') {
|
|
||||||
return A0_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
// %m
|
|
||||||
A0_RETURN_ERR_ON_ERR(a0_str_to_u32(wall_str + 5, wall_str + 7, (uint32_t*)&wall_tm.tm_mon));
|
|
||||||
if (wall_tm.tm_mon < 1 || wall_tm.tm_mon > 12) {
|
|
||||||
return A0_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
wall_tm.tm_mon--;
|
|
||||||
// -
|
|
||||||
if (wall_str[7] != '-') {
|
|
||||||
return A0_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
// %d
|
|
||||||
A0_RETURN_ERR_ON_ERR(a0_str_to_u32(wall_str + 8, wall_str + 10, (uint32_t*)&wall_tm.tm_mday));
|
|
||||||
if (wall_tm.tm_mday < 1 || wall_tm.tm_mday > 31) {
|
|
||||||
return A0_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
// T
|
|
||||||
if (wall_str[10] != 'T') {
|
|
||||||
return A0_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
// %H
|
|
||||||
A0_RETURN_ERR_ON_ERR(a0_str_to_u32(wall_str + 11, wall_str + 13, (uint32_t*)&wall_tm.tm_hour));
|
|
||||||
if (wall_tm.tm_hour > 24) {
|
|
||||||
return A0_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
// :
|
|
||||||
if (wall_str[13] != ':') {
|
|
||||||
return A0_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
// %M
|
|
||||||
A0_RETURN_ERR_ON_ERR(a0_str_to_u32(wall_str + 14, wall_str + 16, (uint32_t*)&wall_tm.tm_min));
|
|
||||||
if (wall_tm.tm_min > 60) {
|
|
||||||
return A0_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
// :
|
|
||||||
if (wall_str[16] != ':') {
|
|
||||||
return A0_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
// %S
|
|
||||||
A0_RETURN_ERR_ON_ERR(a0_str_to_u32(wall_str + 17, wall_str + 19, (uint32_t*)&wall_tm.tm_sec));
|
|
||||||
if (wall_tm.tm_sec > 61) {
|
|
||||||
return A0_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
// .
|
|
||||||
if (wall_str[19] != '.') {
|
|
||||||
return A0_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (memcmp(wall_str + 29, "-00:00", 6) != 0) {
|
|
||||||
return A0_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use timegm, cause it's a pain to compute months/years to seconds.
|
|
||||||
out->ts.tv_sec = timegm(&wall_tm);
|
|
||||||
return a0_str_to_u64(wall_str + 20, wall_str + 29, (uint64_t*)&out->ts.tv_nsec);
|
|
||||||
}
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
/**
|
|
||||||
* \file time.h
|
|
||||||
* \rst
|
|
||||||
*
|
|
||||||
* Mono Time
|
|
||||||
* ---------
|
|
||||||
*
|
|
||||||
* | Mono time is a number of nanoseconds from machine boottime.
|
|
||||||
* | This time cannot decrease and duration between ticks is constant.
|
|
||||||
* | This time is not related to wall clock time.
|
|
||||||
* | This time is most suitable for measuring durations.
|
|
||||||
* |
|
|
||||||
* | As a string, it is represented as a 20 char number:
|
|
||||||
* | **18446744072709551615**
|
|
||||||
* |
|
|
||||||
* | Note that this uses CLOCK_BOOTTIME under the hood, not CLOCK_MONOTONIC.
|
|
||||||
*
|
|
||||||
* Wall Time
|
|
||||||
* ---------
|
|
||||||
*
|
|
||||||
* | Wall time is an time object representing human-readable wall clock time.
|
|
||||||
* | This time can decrease and duration between ticks is not constant.
|
|
||||||
* | This time is most related to wall clock time.
|
|
||||||
* | This time is not suitable for measuring durations.
|
|
||||||
* |
|
|
||||||
* | As a string, it is represented as a 36 char RFC 3999 Nano / ISO 8601:
|
|
||||||
* | **2006-01-02T15:04:05.999999999-07:00**
|
|
||||||
*
|
|
||||||
* \endrst
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef A0_TIME_H
|
|
||||||
#define A0_TIME_H
|
|
||||||
|
|
||||||
#include "a0/err.h"
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/** \addtogroup TIME_MONO
|
|
||||||
* @{
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// Header key for mono timestamps.
|
|
||||||
extern const char A0_TIME_MONO[];
|
|
||||||
|
|
||||||
/// Monotonic timestamp. Despite the name, uses CLOCK_BOOTTIME.
|
|
||||||
typedef struct a0_time_mono_s {
|
|
||||||
struct timespec ts;
|
|
||||||
} a0_time_mono_t;
|
|
||||||
|
|
||||||
/// Get the current mono timestamps.
|
|
||||||
a0_err_t a0_time_mono_now(a0_time_mono_t*);
|
|
||||||
|
|
||||||
/// Stringify a given mono timestamps.
|
|
||||||
a0_err_t a0_time_mono_str(a0_time_mono_t, char mono_str[20]);
|
|
||||||
|
|
||||||
/// Parse a stringified mono timestamps.
|
|
||||||
a0_err_t a0_time_mono_parse(const char mono_str[20], a0_time_mono_t*);
|
|
||||||
|
|
||||||
/// Add a duration in nanoseconds to a mono timestamp.
|
|
||||||
a0_err_t a0_time_mono_add(a0_time_mono_t, int64_t add_nsec, a0_time_mono_t*);
|
|
||||||
|
|
||||||
/** @}*/
|
|
||||||
|
|
||||||
/** \addtogroup TIME_WALL
|
|
||||||
* @{
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// Header key for wall timestamps.
|
|
||||||
extern const char A0_TIME_WALL[];
|
|
||||||
|
|
||||||
/// Wall clock timestamp.
|
|
||||||
typedef struct a0_time_wall_s {
|
|
||||||
struct timespec ts;
|
|
||||||
} a0_time_wall_t;
|
|
||||||
|
|
||||||
/// Get the current wall timestamps.
|
|
||||||
a0_err_t a0_time_wall_now(a0_time_wall_t*);
|
|
||||||
|
|
||||||
/// Stringify a given wall timestamps.
|
|
||||||
a0_err_t a0_time_wall_str(a0_time_wall_t, char wall_str[36]);
|
|
||||||
|
|
||||||
/// Parse a stringified wall timestamps.
|
|
||||||
a0_err_t a0_time_wall_parse(const char wall_str[36], a0_time_wall_t*);
|
|
||||||
|
|
||||||
/** @}*/
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // A0_TRANSPORT_H
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
#ifndef A0_UNUSED_H
|
|
||||||
#define A0_UNUSED_H
|
|
||||||
|
|
||||||
#define A0_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
|
|
||||||
#define A0_MAYBE_UNUSED(X) ((void)(X))
|
|
||||||
|
|
||||||
#endif // A0_UNUSED_H
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "libipc/utility/log.h"
|
|
||||||
#include "libipc/mutex.h"
|
|
||||||
|
|
||||||
#include "get_wait_time.h"
|
|
||||||
#include "sync_obj_impl.h"
|
|
||||||
|
|
||||||
#include "a0/err_macro.h"
|
|
||||||
#include "a0/mtx.h"
|
|
||||||
|
|
||||||
namespace ipc {
|
|
||||||
namespace detail {
|
|
||||||
namespace sync {
|
|
||||||
|
|
||||||
class condition : public sync::obj_impl<a0_cnd_t> {
|
|
||||||
public:
|
|
||||||
condition() = default;
|
|
||||||
~condition() = default;
|
|
||||||
|
|
||||||
bool wait(ipc::sync::mutex &mtx, std::uint64_t tm) noexcept {
|
|
||||||
if (!valid()) return false;
|
|
||||||
if (tm == invalid_value) {
|
|
||||||
int eno = A0_SYSERR(a0_cnd_wait(native(), static_cast<a0_mtx_t *>(mtx.native())));
|
|
||||||
if (eno != 0) {
|
|
||||||
ipc::error("fail condition wait[%d]\n", eno);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
auto ts = 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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
399
src/libipc/platform/posix/shm_posix.cpp → src/libipc/platform/shm_linux.cpp
Normal file → Executable file
399
src/libipc/platform/posix/shm_posix.cpp → src/libipc/platform/shm_linux.cpp
Normal file → Executable file
@ -1,227 +1,172 @@
|
|||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/shm.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/mman.h>
|
||||||
#include <unistd.h>
|
#include <sys/types.h>
|
||||||
#include <fcntl.h>
|
#include <unistd.h>
|
||||||
#include <errno.h>
|
#include <fcntl.h>
|
||||||
|
#include <errno.h>
|
||||||
#include <atomic>
|
|
||||||
#include <string>
|
#include <atomic>
|
||||||
#include <utility>
|
#include <string>
|
||||||
#include <cstring>
|
#include <utility>
|
||||||
|
#include <cstring>
|
||||||
#include "libipc/shm.h"
|
|
||||||
#include "libipc/def.h"
|
#include "libipc/shm.h"
|
||||||
#include "libipc/pool_alloc.h"
|
#include "libipc/def.h"
|
||||||
|
#include "libipc/pool_alloc.h"
|
||||||
#include "libipc/utility/log.h"
|
|
||||||
#include "libipc/memory/resource.h"
|
#include "libipc/utility/log.h"
|
||||||
|
#include "libipc/memory/resource.h"
|
||||||
namespace {
|
|
||||||
|
namespace {
|
||||||
struct info_t {
|
|
||||||
std::atomic<std::int32_t> acc_;
|
struct info_t {
|
||||||
};
|
std::atomic_size_t acc_;
|
||||||
|
};
|
||||||
struct id_info_t {
|
|
||||||
int fd_ = -1;
|
struct id_info_t {
|
||||||
void* mem_ = nullptr;
|
int fd_ = -1;
|
||||||
std::size_t size_ = 0;
|
void* mem_ = nullptr;
|
||||||
ipc::string name_;
|
std::size_t size_ = 0;
|
||||||
};
|
ipc::string name_;
|
||||||
|
};
|
||||||
constexpr std::size_t calc_size(std::size_t size) {
|
|
||||||
return ((((size - 1) / alignof(info_t)) + 1) * alignof(info_t)) + sizeof(info_t);
|
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_;
|
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
|
|
||||||
|
} // internal-linkage
|
||||||
namespace ipc {
|
|
||||||
namespace shm {
|
namespace ipc {
|
||||||
|
namespace shm {
|
||||||
id_t acquire(char const * name, std::size_t size, unsigned mode) {
|
|
||||||
if (!is_valid_string(name)) {
|
id_t acquire(char const * name, std::size_t size, unsigned mode) {
|
||||||
ipc::error("fail acquire: name is empty\n");
|
if (name == nullptr || name[0] == '\0') {
|
||||||
return nullptr;
|
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 = ipc::string{"__IPC_SHM__"} + name;
|
||||||
ipc::string op_name;
|
// Open the object for read-write access.
|
||||||
if (name[0] == '/') {
|
int flag = O_RDWR;
|
||||||
op_name = name;
|
switch (mode) {
|
||||||
} else {
|
case open:
|
||||||
op_name = ipc::string{"/"} + name;
|
size = 0;
|
||||||
}
|
break;
|
||||||
// Open the object for read-write access.
|
// The check for the existence of the object,
|
||||||
int flag = O_RDWR;
|
// and its creation if it does not exist, are performed atomically.
|
||||||
switch (mode) {
|
case create:
|
||||||
case open:
|
flag |= O_CREAT | O_EXCL;
|
||||||
size = 0;
|
break;
|
||||||
break;
|
// Create the shared memory object if it does not exist.
|
||||||
// The check for the existence of the object,
|
default:
|
||||||
// and its creation if it does not exist, are performed atomically.
|
flag |= O_CREAT;
|
||||||
case create:
|
break;
|
||||||
flag |= O_CREAT | O_EXCL;
|
}
|
||||||
break;
|
int fd = ::shm_open(op_name.c_str(), flag, S_IRUSR | S_IWUSR |
|
||||||
// Create the shared memory object if it does not exist.
|
S_IRGRP | S_IWGRP |
|
||||||
default:
|
S_IROTH | S_IWOTH);
|
||||||
flag |= O_CREAT;
|
if (fd == -1) {
|
||||||
break;
|
ipc::error("fail shm_open[%d]: %s\n", errno, name);
|
||||||
}
|
return nullptr;
|
||||||
int fd = ::shm_open(op_name.c_str(), flag, S_IRUSR | S_IWUSR |
|
}
|
||||||
S_IRGRP | S_IWGRP |
|
auto ii = mem::alloc<id_info_t>();
|
||||||
S_IROTH | S_IWOTH);
|
ii->fd_ = fd;
|
||||||
if (fd == -1) {
|
ii->size_ = size;
|
||||||
// only open shm not log error when file not exist
|
ii->name_ = std::move(op_name);
|
||||||
if (open != mode || ENOENT != errno) {
|
return ii;
|
||||||
ipc::error("fail shm_open[%d]: %s\n", errno, op_name.c_str());
|
}
|
||||||
}
|
|
||||||
return nullptr;
|
void * get_mem(id_t id, std::size_t * size) {
|
||||||
}
|
if (id == nullptr) {
|
||||||
::fchmod(fd, S_IRUSR | S_IWUSR |
|
ipc::error("fail get_mem: invalid id (null)\n");
|
||||||
S_IRGRP | S_IWGRP |
|
return nullptr;
|
||||||
S_IROTH | S_IWOTH);
|
}
|
||||||
auto ii = mem::alloc<id_info_t>();
|
auto ii = static_cast<id_info_t*>(id);
|
||||||
ii->fd_ = fd;
|
if (ii->mem_ != nullptr) {
|
||||||
ii->size_ = size;
|
if (size != nullptr) *size = ii->size_;
|
||||||
ii->name_ = std::move(op_name);
|
return ii->mem_;
|
||||||
return ii;
|
}
|
||||||
}
|
int fd = ii->fd_;
|
||||||
|
if (fd == -1) {
|
||||||
std::int32_t get_ref(id_t id) {
|
ipc::error("fail to_mem: invalid id (fd = -1)\n");
|
||||||
if (id == nullptr) {
|
return nullptr;
|
||||||
return 0;
|
}
|
||||||
}
|
if (ii->size_ == 0) {
|
||||||
auto ii = static_cast<id_info_t*>(id);
|
struct stat st;
|
||||||
if (ii->mem_ == nullptr || ii->size_ == 0) {
|
if (::fstat(fd, &st) != 0) {
|
||||||
return 0;
|
ipc::error("fail fstat[%d]: %s, size = %zd\n", errno, ii->name_.c_str(), ii->size_);
|
||||||
}
|
return nullptr;
|
||||||
return acc_of(ii->mem_, ii->size_).load(std::memory_order_acquire);
|
}
|
||||||
}
|
ii->size_ = static_cast<std::size_t>(st.st_size);
|
||||||
|
if ((ii->size_ <= sizeof(info_t)) || (ii->size_ % sizeof(info_t))) {
|
||||||
void sub_ref(id_t id) {
|
ipc::error("fail to_mem: %s, invalid size = %zd\n", ii->name_.c_str(), ii->size_);
|
||||||
if (id == nullptr) {
|
return nullptr;
|
||||||
ipc::error("fail sub_ref: invalid id (null)\n");
|
}
|
||||||
return;
|
}
|
||||||
}
|
else {
|
||||||
auto ii = static_cast<id_info_t*>(id);
|
ii->size_ = calc_size(ii->size_);
|
||||||
if (ii->mem_ == nullptr || ii->size_ == 0) {
|
if (::ftruncate(fd, static_cast<off_t>(ii->size_)) != 0) {
|
||||||
ipc::error("fail sub_ref: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
|
ipc::error("fail ftruncate[%d]: %s, size = %zd\n", errno, ii->name_.c_str(), ii->size_);
|
||||||
return;
|
return nullptr;
|
||||||
}
|
}
|
||||||
acc_of(ii->mem_, ii->size_).fetch_sub(1, std::memory_order_acq_rel);
|
}
|
||||||
}
|
void* mem = ::mmap(nullptr, ii->size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||||
|
if (mem == MAP_FAILED) {
|
||||||
void * get_mem(id_t id, std::size_t * size) {
|
ipc::error("fail mmap[%d]: %s, size = %zd\n", errno, ii->name_.c_str(), ii->size_);
|
||||||
if (id == nullptr) {
|
return nullptr;
|
||||||
ipc::error("fail get_mem: invalid id (null)\n");
|
}
|
||||||
return nullptr;
|
::close(fd);
|
||||||
}
|
ii->fd_ = -1;
|
||||||
auto ii = static_cast<id_info_t*>(id);
|
ii->mem_ = mem;
|
||||||
if (ii->mem_ != nullptr) {
|
if (size != nullptr) *size = ii->size_;
|
||||||
if (size != nullptr) *size = ii->size_;
|
acc_of(mem, ii->size_).fetch_add(1, std::memory_order_release);
|
||||||
return ii->mem_;
|
return mem;
|
||||||
}
|
}
|
||||||
int fd = ii->fd_;
|
|
||||||
if (fd == -1) {
|
void release(id_t id) {
|
||||||
ipc::error("fail get_mem: invalid id (fd = -1)\n");
|
if (id == nullptr) {
|
||||||
return nullptr;
|
ipc::error("fail release: invalid id (null)\n");
|
||||||
}
|
return;
|
||||||
if (ii->size_ == 0) {
|
}
|
||||||
struct stat st;
|
auto ii = static_cast<id_info_t*>(id);
|
||||||
if (::fstat(fd, &st) != 0) {
|
if (ii->mem_ == nullptr || ii->size_ == 0) {
|
||||||
ipc::error("fail fstat[%d]: %s, size = %zd\n", errno, ii->name_.c_str(), ii->size_);
|
ipc::error("fail release: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
|
||||||
return nullptr;
|
}
|
||||||
}
|
else if (acc_of(ii->mem_, ii->size_).fetch_sub(1, std::memory_order_acquire) == 1) {
|
||||||
ii->size_ = static_cast<std::size_t>(st.st_size);
|
::munmap(ii->mem_, ii->size_);
|
||||||
if ((ii->size_ <= sizeof(info_t)) || (ii->size_ % sizeof(info_t))) {
|
if (!ii->name_.empty()) {
|
||||||
ipc::error("fail get_mem: %s, invalid size = %zd\n", ii->name_.c_str(), ii->size_);
|
::shm_unlink(ii->name_.c_str());
|
||||||
return nullptr;
|
}
|
||||||
}
|
}
|
||||||
}
|
else ::munmap(ii->mem_, ii->size_);
|
||||||
else {
|
mem::free(ii);
|
||||||
ii->size_ = calc_size(ii->size_);
|
}
|
||||||
if (::ftruncate(fd, static_cast<off_t>(ii->size_)) != 0) {
|
|
||||||
ipc::error("fail ftruncate[%d]: %s, size = %zd\n", errno, ii->name_.c_str(), ii->size_);
|
void remove(id_t id) {
|
||||||
return nullptr;
|
if (id == nullptr) {
|
||||||
}
|
ipc::error("fail remove: invalid id (null)\n");
|
||||||
}
|
return;
|
||||||
void* mem = ::mmap(nullptr, ii->size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
}
|
||||||
if (mem == MAP_FAILED) {
|
auto ii = static_cast<id_info_t*>(id);
|
||||||
ipc::error("fail mmap[%d]: %s, size = %zd\n", errno, ii->name_.c_str(), ii->size_);
|
auto name = std::move(ii->name_);
|
||||||
return nullptr;
|
release(id);
|
||||||
}
|
if (!name.empty()) {
|
||||||
::close(fd);
|
::shm_unlink(name.c_str());
|
||||||
ii->fd_ = -1;
|
}
|
||||||
ii->mem_ = mem;
|
}
|
||||||
if (size != nullptr) *size = ii->size_;
|
|
||||||
acc_of(mem, ii->size_).fetch_add(1, std::memory_order_release);
|
void remove(char const * name) {
|
||||||
return mem;
|
if (name == nullptr || name[0] == '\0') {
|
||||||
}
|
ipc::error("fail remove: name is empty\n");
|
||||||
|
return;
|
||||||
std::int32_t release(id_t id) noexcept {
|
}
|
||||||
if (id == nullptr) {
|
::shm_unlink((ipc::string{"__IPC_SHM__"} + name).c_str());
|
||||||
ipc::error("fail release: invalid id (null)\n");
|
}
|
||||||
return -1;
|
|
||||||
}
|
} // namespace shm
|
||||||
std::int32_t ret = -1;
|
} // namespace ipc
|
||||||
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());
|
|
||||||
}
|
|
||||||
else if ((ret = acc_of(ii->mem_, ii->size_).fetch_sub(1, std::memory_order_acq_rel)) <= 1) {
|
|
||||||
::munmap(ii->mem_, ii->size_);
|
|
||||||
if (!ii->name_.empty()) {
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else ::munmap(ii->mem_, ii->size_);
|
|
||||||
mem::free(ii);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void remove(id_t id) noexcept {
|
|
||||||
if (id == nullptr) {
|
|
||||||
ipc::error("fail remove: invalid id (null)\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto ii = static_cast<id_info_t*>(id);
|
|
||||||
auto name = std::move(ii->name_);
|
|
||||||
release(id);
|
|
||||||
if (!name.empty()) {
|
|
||||||
int unlink_ret = ::shm_unlink(name.c_str());
|
|
||||||
if (unlink_ret == -1) {
|
|
||||||
ipc::error("fail shm_unlink[%d]: %s\n", errno, name.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void remove(char const * name) noexcept {
|
|
||||||
if (!is_valid_string(name)) {
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace shm
|
|
||||||
} // namespace ipc
|
|
||||||
@ -1,185 +1,125 @@
|
|||||||
|
|
||||||
#if defined(__MINGW32__)
|
#include <Windows.h>
|
||||||
#include <windows.h>
|
|
||||||
#else
|
#include <string>
|
||||||
#include <Windows.h>
|
#include <utility>
|
||||||
#endif
|
|
||||||
|
#include "libipc/shm.h"
|
||||||
#include <atomic>
|
#include "libipc/def.h"
|
||||||
#include <string>
|
#include "libipc/pool_alloc.h"
|
||||||
#include <utility>
|
|
||||||
|
#include "libipc/utility/log.h"
|
||||||
#include "libipc/shm.h"
|
#include "libipc/platform/to_tchar.h"
|
||||||
#include "libipc/def.h"
|
#include "libipc/platform/get_sa.h"
|
||||||
#include "libipc/pool_alloc.h"
|
#include "libipc/memory/resource.h"
|
||||||
|
|
||||||
#include "libipc/utility/log.h"
|
namespace {
|
||||||
#include "libipc/memory/resource.h"
|
|
||||||
|
struct id_info_t {
|
||||||
#include "to_tchar.h"
|
HANDLE h_ = NULL;
|
||||||
#include "get_sa.h"
|
void* mem_ = nullptr;
|
||||||
|
std::size_t size_ = 0;
|
||||||
namespace {
|
};
|
||||||
|
|
||||||
struct info_t {
|
} // internal-linkage
|
||||||
std::atomic<std::int32_t> acc_;
|
|
||||||
};
|
namespace ipc {
|
||||||
|
namespace shm {
|
||||||
struct id_info_t {
|
|
||||||
HANDLE h_ = NULL;
|
id_t acquire(char const * name, std::size_t size, unsigned mode) {
|
||||||
void* mem_ = nullptr;
|
if (name == nullptr || name[0] == '\0') {
|
||||||
std::size_t size_ = 0;
|
ipc::error("fail acquire: name is empty\n");
|
||||||
};
|
return nullptr;
|
||||||
|
}
|
||||||
constexpr std::size_t calc_size(std::size_t size) {
|
HANDLE h;
|
||||||
return ((((size - 1) / alignof(info_t)) + 1) * alignof(info_t)) + sizeof(info_t);
|
auto fmt_name = ipc::detail::to_tchar(ipc::string{"__IPC_SHM__"} + name);
|
||||||
}
|
// Opens a named file mapping object.
|
||||||
|
if (mode == open) {
|
||||||
inline auto& acc_of(void* mem, std::size_t size) {
|
h = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, fmt_name.c_str());
|
||||||
return reinterpret_cast<info_t*>(static_cast<ipc::byte_t*>(mem) + size - sizeof(info_t))->acc_;
|
}
|
||||||
}
|
// Creates or opens a named file mapping object for a specified file.
|
||||||
|
else {
|
||||||
} // internal-linkage
|
h = ::CreateFileMapping(INVALID_HANDLE_VALUE, detail::get_sa(), PAGE_READWRITE | SEC_COMMIT,
|
||||||
|
0, static_cast<DWORD>(size), fmt_name.c_str());
|
||||||
namespace ipc {
|
// If the object exists before the function call, the function returns a handle to the existing object
|
||||||
namespace shm {
|
// (with its current size, not the specified size), and GetLastError returns ERROR_ALREADY_EXISTS.
|
||||||
|
if ((mode == create) && (::GetLastError() == ERROR_ALREADY_EXISTS)) {
|
||||||
id_t acquire(char const * name, std::size_t size, unsigned mode) {
|
::CloseHandle(h);
|
||||||
if (!is_valid_string(name)) {
|
h = NULL;
|
||||||
ipc::error("fail acquire: name is empty\n");
|
}
|
||||||
return nullptr;
|
}
|
||||||
}
|
if (h == NULL) {
|
||||||
HANDLE h;
|
ipc::error("fail CreateFileMapping/OpenFileMapping[%d]: %s\n", static_cast<int>(::GetLastError()), name);
|
||||||
auto fmt_name = ipc::detail::to_tchar(name);
|
return nullptr;
|
||||||
// Opens a named file mapping object.
|
}
|
||||||
if (mode == open) {
|
auto ii = mem::alloc<id_info_t>();
|
||||||
h = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, fmt_name.c_str());
|
ii->h_ = h;
|
||||||
if (h == NULL) {
|
ii->size_ = size;
|
||||||
ipc::error("fail OpenFileMapping[%d]: %s\n", static_cast<int>(::GetLastError()), name);
|
return ii;
|
||||||
return nullptr;
|
}
|
||||||
}
|
|
||||||
}
|
void * get_mem(id_t id, std::size_t * size) {
|
||||||
// Creates or opens a named file mapping object for a specified file.
|
if (id == nullptr) {
|
||||||
else {
|
ipc::error("fail get_mem: invalid id (null)\n");
|
||||||
std::size_t alloc_size = calc_size(size);
|
return nullptr;
|
||||||
h = ::CreateFileMapping(INVALID_HANDLE_VALUE, detail::get_sa(), PAGE_READWRITE | SEC_COMMIT,
|
}
|
||||||
0, static_cast<DWORD>(alloc_size), fmt_name.c_str());
|
auto ii = static_cast<id_info_t*>(id);
|
||||||
DWORD err = ::GetLastError();
|
if (ii->mem_ != nullptr) {
|
||||||
// If the object exists before the function call, the function returns a handle to the existing object
|
if (size != nullptr) *size = ii->size_;
|
||||||
// (with its current size, not the specified size), and GetLastError returns ERROR_ALREADY_EXISTS.
|
return ii->mem_;
|
||||||
if ((mode == create) && (err == ERROR_ALREADY_EXISTS)) {
|
}
|
||||||
if (h != NULL) ::CloseHandle(h);
|
if (ii->h_ == NULL) {
|
||||||
h = NULL;
|
ipc::error("fail to_mem: invalid id (h = null)\n");
|
||||||
}
|
return nullptr;
|
||||||
if (h == NULL) {
|
}
|
||||||
ipc::error("fail CreateFileMapping[%d]: %s\n", static_cast<int>(err), name);
|
LPVOID mem = ::MapViewOfFile(ii->h_, FILE_MAP_ALL_ACCESS, 0, 0, 0);
|
||||||
return nullptr;
|
if (mem == NULL) {
|
||||||
}
|
ipc::error("fail MapViewOfFile[%d]\n", static_cast<int>(::GetLastError()));
|
||||||
}
|
return nullptr;
|
||||||
auto ii = mem::alloc<id_info_t>();
|
}
|
||||||
ii->h_ = h;
|
MEMORY_BASIC_INFORMATION mem_info;
|
||||||
ii->size_ = size;
|
if (::VirtualQuery(mem, &mem_info, sizeof(mem_info)) == 0) {
|
||||||
return ii;
|
ipc::error("fail VirtualQuery[%d]\n", static_cast<int>(::GetLastError()));
|
||||||
}
|
return nullptr;
|
||||||
|
}
|
||||||
std::int32_t get_ref(id_t id) {
|
ii->mem_ = mem;
|
||||||
if (id == nullptr) {
|
ii->size_ = static_cast<std::size_t>(mem_info.RegionSize);
|
||||||
return 0;
|
if (size != nullptr) *size = ii->size_;
|
||||||
}
|
return static_cast<void *>(mem);
|
||||||
auto ii = static_cast<id_info_t*>(id);
|
}
|
||||||
if (ii->mem_ == nullptr || ii->size_ == 0) {
|
|
||||||
return 0;
|
void release(id_t id) {
|
||||||
}
|
if (id == nullptr) {
|
||||||
return acc_of(ii->mem_, calc_size(ii->size_)).load(std::memory_order_acquire);
|
ipc::error("fail release: invalid id (null)\n");
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
void sub_ref(id_t id) {
|
auto ii = static_cast<id_info_t*>(id);
|
||||||
if (id == nullptr) {
|
if (ii->mem_ == nullptr || ii->size_ == 0) {
|
||||||
ipc::error("fail sub_ref: invalid id (null)\n");
|
ipc::error("fail release: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
|
||||||
return;
|
}
|
||||||
}
|
else ::UnmapViewOfFile(static_cast<LPCVOID>(ii->mem_));
|
||||||
auto ii = static_cast<id_info_t*>(id);
|
if (ii->h_ == NULL) {
|
||||||
if (ii->mem_ == nullptr || ii->size_ == 0) {
|
ipc::error("fail release: invalid id (h = null)\n");
|
||||||
ipc::error("fail sub_ref: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
|
}
|
||||||
return;
|
else ::CloseHandle(ii->h_);
|
||||||
}
|
mem::free(ii);
|
||||||
acc_of(ii->mem_, calc_size(ii->size_)).fetch_sub(1, std::memory_order_acq_rel);
|
}
|
||||||
}
|
|
||||||
|
void remove(id_t id) {
|
||||||
void * get_mem(id_t id, std::size_t * size) {
|
if (id == nullptr) {
|
||||||
if (id == nullptr) {
|
ipc::error("fail release: invalid id (null)\n");
|
||||||
ipc::error("fail get_mem: invalid id (null)\n");
|
return;
|
||||||
return nullptr;
|
}
|
||||||
}
|
release(id);
|
||||||
auto ii = static_cast<id_info_t*>(id);
|
}
|
||||||
if (ii->mem_ != nullptr) {
|
|
||||||
if (size != nullptr) *size = ii->size_;
|
void remove(char const * name) {
|
||||||
return ii->mem_;
|
if (name == nullptr || name[0] == '\0') {
|
||||||
}
|
ipc::error("fail remove: name is empty\n");
|
||||||
if (ii->h_ == NULL) {
|
return;
|
||||||
ipc::error("fail to_mem: invalid id (h = null)\n");
|
}
|
||||||
return nullptr;
|
// Do Nothing.
|
||||||
}
|
}
|
||||||
LPVOID mem = ::MapViewOfFile(ii->h_, FILE_MAP_ALL_ACCESS, 0, 0, 0);
|
|
||||||
if (mem == NULL) {
|
} // namespace shm
|
||||||
ipc::error("fail MapViewOfFile[%d]\n", static_cast<int>(::GetLastError()));
|
} // namespace ipc
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
MEMORY_BASIC_INFORMATION mem_info;
|
|
||||||
if (::VirtualQuery(mem, &mem_info, sizeof(mem_info)) == 0) {
|
|
||||||
ipc::error("fail VirtualQuery[%d]\n", static_cast<int>(::GetLastError()));
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
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 {
|
|
||||||
if (id == nullptr) {
|
|
||||||
ipc::error("fail release: invalid id (null)\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
std::int32_t ret = -1;
|
|
||||||
auto ii = static_cast<id_info_t*>(id);
|
|
||||||
if (ii->mem_ == nullptr || ii->size_ == 0) {
|
|
||||||
ipc::error("fail release: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ret = acc_of(ii->mem_, calc_size(ii->size_)).fetch_sub(1, std::memory_order_acq_rel);
|
|
||||||
::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 {
|
|
||||||
if (id == nullptr) {
|
|
||||||
ipc::error("fail release: invalid id (null)\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
release(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
void remove(char const * name) noexcept {
|
|
||||||
if (!is_valid_string(name)) {
|
|
||||||
ipc::error("fail remove: name is empty\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Do Nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace shm
|
|
||||||
} // namespace ipc
|
|
||||||
@ -1,10 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#if defined(__MINGW32__)
|
|
||||||
#include <windows.h>
|
|
||||||
#else
|
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -15,8 +11,8 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
#include "libipc/utility/concept.h"
|
#include "libipc/utility/concept.h"
|
||||||
#include "libipc/memory/resource.h"
|
|
||||||
#include "libipc/platform/detail.h"
|
#include "libipc/platform/detail.h"
|
||||||
|
#include "libipc/memory/resource.h"
|
||||||
|
|
||||||
namespace ipc {
|
namespace ipc {
|
||||||
namespace detail {
|
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
|
* codecvt_utf8_utf16/std::wstring_convert is deprecated
|
||||||
* \see https://codingtidbit.com/2020/02/09/c17-codecvt_utf8-is-deprecated/
|
* @see https://codingtidbit.com/2020/02/09/c17-codecvt_utf8-is-deprecated/
|
||||||
* https://stackoverflow.com/questions/42946335/deprecated-header-codecvt-replacement
|
* https://stackoverflow.com/questions/42946335/deprecated-header-codecvt-replacement
|
||||||
* https://en.cppreference.com/w/cpp/locale/codecvt/in
|
* https://en.cppreference.com/w/cpp/locale/codecvt/in
|
||||||
* https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar
|
* https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar
|
||||||
425
src/libipc/platform/waiter_linux.h
Executable file
425
src/libipc/platform/waiter_linux.h
Executable 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
233
src/libipc/platform/waiter_win.h
Executable 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
|
||||||
292
src/libipc/platform/waiter_wrapper.h
Executable file
292
src/libipc/platform/waiter_wrapper.h
Executable 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
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -18,7 +18,6 @@
|
|||||||
#include "libipc/utility/log.h"
|
#include "libipc/utility/log.h"
|
||||||
#include "libipc/platform/detail.h"
|
#include "libipc/platform/detail.h"
|
||||||
#include "libipc/circ/elem_def.h"
|
#include "libipc/circ/elem_def.h"
|
||||||
#include "libipc/memory/resource.h"
|
|
||||||
|
|
||||||
namespace ipc {
|
namespace ipc {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
@ -30,7 +29,7 @@ protected:
|
|||||||
|
|
||||||
template <typename Elems>
|
template <typename Elems>
|
||||||
Elems* open(char const * name) {
|
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");
|
ipc::error("fail open waiter: name is empty!\n");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@ -55,17 +54,8 @@ public:
|
|||||||
queue_conn(const queue_conn&) = delete;
|
queue_conn(const queue_conn&) = delete;
|
||||||
queue_conn& operator=(const queue_conn&) = delete;
|
queue_conn& operator=(const queue_conn&) = delete;
|
||||||
|
|
||||||
void clear() noexcept {
|
bool connected() const noexcept {
|
||||||
elems_h_.clear();
|
return connected_ != 0;
|
||||||
}
|
|
||||||
|
|
||||||
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_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
circ::cc_t connected_id() const noexcept {
|
circ::cc_t connected_id() const noexcept {
|
||||||
@ -78,16 +68,16 @@ public:
|
|||||||
-> std::tuple<bool, bool, decltype(std::declval<Elems>().cursor())> {
|
-> std::tuple<bool, bool, decltype(std::declval<Elems>().cursor())> {
|
||||||
if (elems == nullptr) return {};
|
if (elems == nullptr) return {};
|
||||||
// if it's already connected, just 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();
|
connected_ = elems->connect_receiver();
|
||||||
return {connected(elems), true, elems->cursor()};
|
return {connected(), true, elems->cursor()};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Elems>
|
template <typename Elems>
|
||||||
bool disconnect(Elems* elems) noexcept {
|
bool disconnect(Elems* elems) noexcept {
|
||||||
if (elems == nullptr) return false;
|
if (elems == nullptr) return false;
|
||||||
// if it's already disconnected, just 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));
|
elems->disconnect_receiver(std::exchange(connected_, 0));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -113,7 +103,7 @@ public:
|
|||||||
|
|
||||||
explicit queue_base(char const * name)
|
explicit queue_base(char const * name)
|
||||||
: queue_base{} {
|
: queue_base{} {
|
||||||
elems_ = queue_conn::template open<elems_t>(name);
|
elems_ = open<elems_t>(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
explicit queue_base(elems_t * elems) noexcept
|
explicit queue_base(elems_t * elems) noexcept
|
||||||
@ -126,17 +116,6 @@ public:
|
|||||||
base_t::close();
|
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 * elems() noexcept { return elems_; }
|
||||||
elems_t const * elems() const noexcept { return elems_; }
|
elems_t const * elems() const noexcept { return elems_; }
|
||||||
|
|
||||||
@ -151,10 +130,6 @@ public:
|
|||||||
elems_->disconnect_sender();
|
elems_->disconnect_sender();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool connected() const noexcept {
|
|
||||||
return base_t::connected(elems_);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool connect() noexcept {
|
bool connect() noexcept {
|
||||||
auto tp = base_t::connect(elems_);
|
auto tp = base_t::connect(elems_);
|
||||||
if (std::get<0>(tp) && std::get<1>(tp)) {
|
if (std::get<0>(tp) && std::get<1>(tp)) {
|
||||||
@ -169,7 +144,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::size_t conn_count() const noexcept {
|
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 {
|
bool valid() const noexcept {
|
||||||
|
|||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -3,7 +3,6 @@
|
|||||||
#include <new>
|
#include <new>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "libipc/platform/detail.h"
|
|
||||||
#include "libipc/utility/concept.h"
|
#include "libipc/utility/concept.h"
|
||||||
#include "libipc/pool_alloc.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>;
|
using IsImplUncomfortable = ipc::require<(sizeof(T) > sizeof(T*)), R>;
|
||||||
|
|
||||||
template <typename T, typename... P>
|
template <typename T, typename... P>
|
||||||
IPC_CONSTEXPR_ auto make_impl(P&&... params) -> IsImplComfortable<T> {
|
constexpr auto make_impl(P&&... params) -> IsImplComfortable<T> {
|
||||||
T* buf {};
|
T* buf {};
|
||||||
::new (&buf) T { std::forward<P>(params)... };
|
::new (&buf) T { std::forward<P>(params)... };
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
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)));
|
return reinterpret_cast<T*>(&const_cast<char &>(reinterpret_cast<char const &>(p)));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
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();
|
if (p != nullptr) impl(p)->~T();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, typename... P>
|
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)...);
|
return mem::alloc<T>(std::forward<P>(params)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
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);
|
mem::free(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
IPC_CONSTEXPR_ auto impl(T* const (& p)) -> IsImplUncomfortable<T> {
|
constexpr auto impl(T* const (& p)) -> IsImplUncomfortable<T> {
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct pimpl {
|
struct pimpl {
|
||||||
template <typename... P>
|
template <typename... P>
|
||||||
IPC_CONSTEXPR_ static T* make(P&&... params) {
|
constexpr static T* make(P&&... params) {
|
||||||
return make_impl<T>(std::forward<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)));
|
clear_impl(static_cast<T*>(const_cast<pimpl*>(this)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <utility> // std::forward, std::integer_sequence
|
#include <utility> // std::forward, std::integer_sequence
|
||||||
#include <cstddef> // std::size_t
|
#include <cstddef> // std::size_t
|
||||||
#include <new> // std::hardware_destructive_interference_size
|
#include <new> // std::hardware_destructive_interference_size
|
||||||
#include <type_traits> // std::is_trivially_copyable
|
|
||||||
|
|
||||||
#include "libipc/platform/detail.h"
|
#include "libipc/platform/detail.h"
|
||||||
|
|
||||||
@ -45,15 +44,13 @@ enum {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <typename T, typename U>
|
template <typename T, typename U>
|
||||||
auto horrible_cast(U rhs) noexcept
|
T horrible_cast(U val) {
|
||||||
-> typename std::enable_if<std::is_trivially_copyable<T>::value
|
|
||||||
&& std::is_trivially_copyable<U>::value, T>::type {
|
|
||||||
union {
|
union {
|
||||||
T t;
|
T out;
|
||||||
U u;
|
U in;
|
||||||
} r = {};
|
} u;
|
||||||
r.u = rhs;
|
u.in = val;
|
||||||
return r.t;
|
return u.out;
|
||||||
}
|
}
|
||||||
|
|
||||||
IPC_CONSTEXPR_ std::size_t make_align(std::size_t align, std::size_t size) {
|
IPC_CONSTEXPR_ std::size_t make_align(std::size_t align, std::size_t size) {
|
||||||
|
|||||||
@ -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
129
src/libipc/waiter_helper.h
Normal 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
71
src/libipc/waiter_template.inc
Executable 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();
|
||||||
|
}
|
||||||
@ -5,11 +5,11 @@
|
|||||||
namespace ipc {
|
namespace ipc {
|
||||||
namespace mem {
|
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);
|
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);
|
async_pool_alloc::free(p, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5,7 +5,6 @@
|
|||||||
#include "libipc/shm.h"
|
#include "libipc/shm.h"
|
||||||
|
|
||||||
#include "libipc/utility/pimpl.h"
|
#include "libipc/utility/pimpl.h"
|
||||||
#include "libipc/utility/log.h"
|
|
||||||
#include "libipc/memory/resource.h"
|
#include "libipc/memory/resource.h"
|
||||||
|
|
||||||
namespace ipc {
|
namespace ipc {
|
||||||
@ -48,61 +47,28 @@ handle& handle::operator=(handle rhs) {
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool handle::valid() const noexcept {
|
bool handle::valid() const {
|
||||||
return impl(p_)->m_ != nullptr;
|
return impl(p_)->m_ != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t handle::size() const noexcept {
|
std::size_t handle::size() const {
|
||||||
return impl(p_)->s_;
|
return impl(p_)->s_;
|
||||||
}
|
}
|
||||||
|
|
||||||
char const * handle::name() const noexcept {
|
char const * handle::name() const {
|
||||||
return impl(p_)->n_.c_str();
|
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) {
|
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();
|
release();
|
||||||
const auto id = shm::acquire(name, size, mode);
|
impl(p_)->id_ = shm::acquire((impl(p_)->n_ = name).c_str(), size, mode);
|
||||||
if (!id) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
impl(p_)->id_ = id;
|
|
||||||
impl(p_)->n_ = name;
|
|
||||||
impl(p_)->m_ = shm::get_mem(impl(p_)->id_, &(impl(p_)->s_));
|
impl(p_)->m_ = shm::get_mem(impl(p_)->id_, &(impl(p_)->s_));
|
||||||
return valid();
|
return valid();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::int32_t handle::release() {
|
void handle::release() {
|
||||||
if (impl(p_)->id_ == nullptr) return -1;
|
|
||||||
return shm::release(detach());
|
|
||||||
}
|
|
||||||
|
|
||||||
void handle::clear() noexcept {
|
|
||||||
if (impl(p_)->id_ == nullptr) return;
|
if (impl(p_)->id_ == nullptr) return;
|
||||||
shm::remove(detach());
|
shm::release(detach());
|
||||||
}
|
|
||||||
|
|
||||||
void handle::clear_storage(char const * name) noexcept {
|
|
||||||
if (name == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
shm::remove(name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void* handle::get() const {
|
void* handle::get() const {
|
||||||
77
src/waiter.cpp
Executable file
77
src/waiter.cpp
Executable 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
11
test/CMakeLists.txt
Normal file → Executable file
@ -15,15 +15,8 @@ include_directories(
|
|||||||
${LIBIPC_PROJECT_DIR}/3rdparty
|
${LIBIPC_PROJECT_DIR}/3rdparty
|
||||||
${LIBIPC_PROJECT_DIR}/3rdparty/gtest/include)
|
${LIBIPC_PROJECT_DIR}/3rdparty/gtest/include)
|
||||||
|
|
||||||
# Collect only new test files (exclude archive directory)
|
file(GLOB SRC_FILES ${LIBIPC_PROJECT_DIR}/test/*.cpp)
|
||||||
file(GLOB SRC_FILES
|
file(GLOB HEAD_FILES ${LIBIPC_PROJECT_DIR}/test/*.h)
|
||||||
${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")
|
|
||||||
|
|
||||||
add_executable(${PROJECT_NAME} ${SRC_FILES} ${HEAD_FILES})
|
add_executable(${PROJECT_NAME} ${SRC_FILES} ${HEAD_FILES})
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
|
||||||
@ -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
|
|
||||||
@ -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();
|
|
||||||
}
|
|
||||||
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,110 +1,86 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
#include "capo/stopwatch.hpp"
|
#include "capo/stopwatch.hpp"
|
||||||
|
|
||||||
#include "thread_pool.h"
|
#include "thread_pool.h"
|
||||||
|
|
||||||
#include "libipc/platform/detail.h"
|
namespace ipc_ut {
|
||||||
#ifdef IPC_OS_LINUX_
|
|
||||||
#include <fcntl.h> // ::open
|
template <typename Dur>
|
||||||
#endif
|
struct unit;
|
||||||
|
|
||||||
namespace ipc_ut {
|
template <> struct unit<std::chrono::nanoseconds> {
|
||||||
|
constexpr static char const * str() noexcept {
|
||||||
template <typename Dur>
|
return "ns";
|
||||||
struct unit;
|
}
|
||||||
|
};
|
||||||
template <> struct unit<std::chrono::nanoseconds> {
|
|
||||||
constexpr static char const * str() noexcept {
|
template <> struct unit<std::chrono::microseconds> {
|
||||||
return "ns";
|
constexpr static char const * str() noexcept {
|
||||||
}
|
return "us";
|
||||||
};
|
}
|
||||||
|
};
|
||||||
template <> struct unit<std::chrono::microseconds> {
|
|
||||||
constexpr static char const * str() noexcept {
|
template <> struct unit<std::chrono::milliseconds> {
|
||||||
return "us";
|
constexpr static char const * str() noexcept {
|
||||||
}
|
return "ms";
|
||||||
};
|
}
|
||||||
|
};
|
||||||
template <> struct unit<std::chrono::milliseconds> {
|
|
||||||
constexpr static char const * str() noexcept {
|
template <> struct unit<std::chrono::seconds> {
|
||||||
return "ms";
|
constexpr static char const * str() noexcept {
|
||||||
}
|
return "sec";
|
||||||
};
|
}
|
||||||
|
};
|
||||||
template <> struct unit<std::chrono::seconds> {
|
|
||||||
constexpr static char const * str() noexcept {
|
struct test_stopwatch {
|
||||||
return "sec";
|
capo::stopwatch<> sw_;
|
||||||
}
|
std::atomic_flag started_ = ATOMIC_FLAG_INIT;
|
||||||
};
|
|
||||||
|
void start() {
|
||||||
struct test_stopwatch {
|
if (!started_.test_and_set()) {
|
||||||
capo::stopwatch<> sw_;
|
sw_.start();
|
||||||
std::atomic_flag started_ = ATOMIC_FLAG_INIT;
|
}
|
||||||
|
}
|
||||||
void start() {
|
|
||||||
if (!started_.test_and_set()) {
|
template <typename ToDur = std::chrono::nanoseconds>
|
||||||
sw_.start();
|
void print_elapsed(int N, int Loops, char const * message = "") {
|
||||||
}
|
auto ts = sw_.elapsed<ToDur>();
|
||||||
}
|
std::cout << "[" << N << ", \t" << Loops << "] " << message << "\t"
|
||||||
|
<< (double(ts) / double(Loops)) << " " << unit<ToDur>::str() << std::endl;
|
||||||
template <typename ToDur = std::chrono::nanoseconds>
|
}
|
||||||
void print_elapsed(int N, int Loops, char const * message = "") {
|
|
||||||
auto ts = sw_.elapsed<ToDur>();
|
template <int Factor, typename ToDur = std::chrono::nanoseconds>
|
||||||
std::cout << "[" << N << ", \t" << Loops << "] " << message << "\t"
|
void print_elapsed(int N, int M, int Loops, char const * message = "") {
|
||||||
<< (double(ts) / double(Loops)) << " " << unit<ToDur>::str() << std::endl;
|
auto ts = sw_.elapsed<ToDur>();
|
||||||
}
|
std::cout << "[" << N << "-" << M << ", \t" << Loops << "] " << message << "\t"
|
||||||
|
<< (double(ts) / double(Factor ? (Loops * Factor) : (Loops * N))) << " " << unit<ToDur>::str() << std::endl;
|
||||||
template <int Factor, typename ToDur = std::chrono::nanoseconds>
|
}
|
||||||
void print_elapsed(int N, int M, int Loops, char const * message = "") {
|
|
||||||
auto ts = sw_.elapsed<ToDur>();
|
template <typename ToDur = std::chrono::nanoseconds>
|
||||||
std::cout << "[" << N << "-" << M << ", \t" << Loops << "] " << message << "\t"
|
void print_elapsed(int N, int M, int Loops, char const * message = "") {
|
||||||
<< (double(ts) / double(Factor ? (Loops * Factor) : (Loops * N))) << " " << unit<ToDur>::str() << std::endl;
|
print_elapsed<0, ToDur>(N, M, Loops, message);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
template <typename ToDur = std::chrono::nanoseconds>
|
|
||||||
void print_elapsed(int N, int M, int Loops, char const * message = "") {
|
inline static thread_pool & sender() {
|
||||||
print_elapsed<0, ToDur>(N, M, Loops, message);
|
static thread_pool pool;
|
||||||
}
|
return pool;
|
||||||
};
|
}
|
||||||
|
|
||||||
inline static thread_pool & sender() {
|
inline static thread_pool & reader() {
|
||||||
static thread_pool pool;
|
static thread_pool pool;
|
||||||
return pool;
|
return pool;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline static thread_pool & reader() {
|
} // namespace ipc_ut
|
||||||
static thread_pool pool;
|
|
||||||
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
|
|
||||||
@ -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);
|
|
||||||
}
|
|
||||||
@ -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();
|
|
||||||
}
|
|
||||||
@ -18,9 +18,8 @@ using namespace ipc;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr int LoopCount = 10000;
|
constexpr int LoopCount = 10000;
|
||||||
constexpr int MultiMax = 8;
|
constexpr int MultiMax = 8;
|
||||||
constexpr int TestBuffMax = 65536;
|
|
||||||
|
|
||||||
struct msg_head {
|
struct msg_head {
|
||||||
int id_;
|
int id_;
|
||||||
@ -29,7 +28,7 @@ struct msg_head {
|
|||||||
class rand_buf : public buffer {
|
class rand_buf : public buffer {
|
||||||
public:
|
public:
|
||||||
rand_buf() {
|
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) {
|
*this = buffer(new char[size], size, [](void * p, std::size_t) {
|
||||||
delete [] static_cast<char *>(p);
|
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) {
|
for (int k = 0; k < s_cnt; ++k) {
|
||||||
ipc_ut::sender() << [name, &sw, r_cnt, k] {
|
ipc_ut::sender() << [name, &sw, r_cnt, k] {
|
||||||
Que que { name, ipc::sender };
|
Que que { name, ipc::sender };
|
||||||
ASSERT_TRUE(que.wait_for_recv(r_cnt));
|
EXPECT_TRUE(que.wait_for_recv(r_cnt));
|
||||||
sw.start();
|
sw.start();
|
||||||
for (int i = 0; i < (int)data_set__.get().size(); ++i) {
|
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) {
|
if (data_set != got) {
|
||||||
printf("data_set__.get()[%d] != got, size = %zd/%zd\n",
|
printf("data_set__.get()[%d] != got, size = %zd/%zd\n",
|
||||||
i, data_set.size(), got.size());
|
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();
|
ipc_ut::sender().wait_for_done();
|
||||||
Que que { name };
|
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) {
|
for (int k = 0; k < r_cnt; ++k) {
|
||||||
que.send(rand_buf{msg_head{-1}});
|
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
|
} // internal-linkage
|
||||||
|
|
||||||
TEST(IPC, clear) {
|
TEST(IPC, basic) {
|
||||||
{
|
|
||||||
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_basic<relat::single, relat::single, trans::unicast >("ssu");
|
test_basic<relat::single, relat::single, trans::unicast >("ssu");
|
||||||
}
|
test_basic<relat::single, relat::multi , trans::unicast >("smu");
|
||||||
|
test_basic<relat::multi , relat::multi , trans::unicast >("mmu");
|
||||||
TEST(IPC, basic_smb) {
|
|
||||||
test_basic<relat::single, relat::multi , trans::broadcast>("smb");
|
test_basic<relat::single, relat::multi , trans::broadcast>("smb");
|
||||||
}
|
|
||||||
|
|
||||||
TEST(IPC, basic_mmb) {
|
|
||||||
test_basic<relat::multi , relat::multi , trans::broadcast>("mmb");
|
test_basic<relat::multi , relat::multi , trans::broadcast>("mmb");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(IPC, 1v1) {
|
TEST(IPC, 1v1) {
|
||||||
test_sr<relat::single, relat::single, trans::unicast >("ssu", 1, 1);
|
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::single, relat::multi , trans::unicast >("smu", 1, 1);
|
||||||
//test_sr<relat::multi , relat::multi , trans::unicast >("mmu", 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::single, relat::multi , trans::broadcast>("smb", 1, 1);
|
||||||
test_sr<relat::multi , relat::multi , trans::broadcast>("mmb", 1, 1);
|
test_sr<relat::multi , relat::multi , trans::broadcast>("mmb", 1, 1);
|
||||||
}
|
}
|
||||||
@ -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);
|
|
||||||
}
|
|
||||||
@ -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
Loading…
x
Reference in New Issue
Block a user