mirror of
https://github.com/mutouyun/cpp-ipc.git
synced 2025-12-07 01:06:45 +08:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce0773b3e6 | ||
|
|
addfe4f5cf | ||
|
|
2593213d9c | ||
|
|
37f16cea47 | ||
|
|
0ba12144bd | ||
|
|
5517f48681 | ||
|
|
47fa303455 | ||
|
|
a82e3faf63 | ||
|
|
543672b39d | ||
|
|
006a46e09f | ||
|
|
f8e71a548c | ||
|
|
cf5738eb3a | ||
|
|
c31ef988c1 | ||
|
|
7726742157 | ||
|
|
b9dd75ccd9 | ||
|
|
e66bd880e9 | ||
|
|
ff74cdd57a | ||
|
|
78be284668 | ||
|
|
d5f787596a | ||
|
|
0ecf1a4137 | ||
|
|
91e4489a55 | ||
|
|
de76cf80d5 | ||
|
|
8103c117f1 | ||
|
|
7447a86d41 | ||
|
|
7092df53bb | ||
|
|
2cde78d692 | ||
|
|
b5146655fa | ||
|
|
9070a899ef | ||
|
|
c21138b5b4 | ||
|
|
4832c47345 | ||
|
|
6e17ce184b | ||
|
|
b4ad3c69c9 | ||
|
|
280a21c89a | ||
|
|
3d743d57ac | ||
|
|
17eaa573ca | ||
|
|
0d53a3cdb1 | ||
|
|
c5302d00ab | ||
|
|
72c4b5abc4 | ||
|
|
a0c7725a14 | ||
|
|
a1cdc9a711 | ||
|
|
87b1fa4abc | ||
|
|
120d85a2c4 | ||
|
|
a6c7c8542f | ||
|
|
fdcc9340be | ||
|
|
f3bf137668 | ||
|
|
7bb5f2e611 | ||
|
|
06d4aec320 | ||
|
|
5c36b1264f | ||
|
|
d69093462a | ||
|
|
df09c22738 | ||
|
|
2673453e66 | ||
|
|
84bb801b6e | ||
|
|
5e5b347636 | ||
|
|
28fdf17279 | ||
|
|
17dcde92bf | ||
|
|
ab90437e44 | ||
|
|
acea9d74da | ||
|
|
e1f377d7f6 | ||
|
|
29678f1d41 | ||
|
|
5071fb5db6 | ||
|
|
805490605e | ||
|
|
025311d5f6 | ||
|
|
035d76d5aa | ||
|
|
144b2db9ca | ||
|
|
c4280efd5f |
@ -5,13 +5,13 @@
|
|||||||
[](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)
|
[](https://github.com/microsoft/vcpkg/tree/master/ports/cpp-ipc)
|
||||||
|
|
||||||
## A high-performance inter-process communication library using shared memory on Linux/Windows.
|
## A high-performance inter-process communication library using shared memory on Linux/Windows/FreeBSD.
|
||||||
|
|
||||||
* Compilers with C++17 support are recommended (msvc-2017/gcc-7/clang-4)
|
* Compilers with C++17 support are recommended (msvc-2017/gcc-7/clang-4)
|
||||||
* No other dependencies except STL.
|
* No other dependencies except STL.
|
||||||
* Only lock-free or lightweight spin-lock is used.
|
* Only lock-free or lightweight spin-lock is used.
|
||||||
* Circular array is used as the underline data structure.
|
* Circular array is used as the underline data structure.
|
||||||
* `ipc::route` supports single read and multiple write. `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` supports single write and multiple read. `ipc::channel` supports multiple read and write. (**Note: currently, a channel supports up to 32 receivers, but there is no such a limit for the sender.**)
|
||||||
* Broadcasting is used by default, but user can choose any read/ write combinations.
|
* 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.)
|
* 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`
|
* [Vcpkg](https://github.com/microsoft/vcpkg/blob/master/README.md) way of installation is supported. E.g. `vcpkg install cpp-ipc`
|
||||||
@ -44,7 +44,7 @@ Performance data: [performance.xlsx](performance.xlsx)
|
|||||||
------
|
------
|
||||||
|
|
||||||
|
|
||||||
## 使用共享内存的跨平台(Linux/Windows,x86/x64/ARM)高性能IPC通讯库
|
## 使用共享内存的跨平台(Linux/Windows/FreeBSD,x86/x64/ARM)高性能IPC通讯库
|
||||||
|
|
||||||
* 推荐支持C++17的编译器(msvc-2017/gcc-7/clang-4)
|
* 推荐支持C++17的编译器(msvc-2017/gcc-7/clang-4)
|
||||||
* 除STL外,无其他依赖
|
* 除STL外,无其他依赖
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
/// \brief To create a basic Windows command line program.
|
/// \brief To create a basic Windows command line program.
|
||||||
|
|
||||||
|
#if defined(__MINGW32__)
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
#include <tchar.h>
|
#include <tchar.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
/// \brief To create a basic Windows Service in C++.
|
/// \brief To create a basic Windows Service in C++.
|
||||||
/// \see https://www.codeproject.com/Articles/499465/Simple-Windows-Service-in-Cplusplus
|
/// \see https://www.codeproject.com/Articles/499465/Simple-Windows-Service-in-Cplusplus
|
||||||
|
|
||||||
|
#if defined(__MINGW32__)
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
#include <tchar.h>
|
#include <tchar.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
|||||||
@ -17,14 +17,16 @@ public:
|
|||||||
buffer();
|
buffer();
|
||||||
|
|
||||||
buffer(void* p, std::size_t s, destructor_t d);
|
buffer(void* p, std::size_t s, destructor_t d);
|
||||||
buffer(void* p, std::size_t s, destructor_t d, void* additional);
|
// mem_to_free: pointer to be passed to destructor (if different from p)
|
||||||
|
// Use case: when p points into a larger allocated block that needs to be freed
|
||||||
|
buffer(void* p, std::size_t s, destructor_t d, void* mem_to_free);
|
||||||
buffer(void* p, std::size_t s);
|
buffer(void* p, std::size_t s);
|
||||||
|
|
||||||
template <std::size_t N>
|
template <std::size_t N>
|
||||||
explicit buffer(byte_t const (& data)[N])
|
explicit buffer(byte_t (& data)[N])
|
||||||
: buffer(data, sizeof(data)) {
|
: buffer(data, sizeof(data)) {
|
||||||
}
|
}
|
||||||
explicit buffer(char const & c);
|
explicit buffer(char & c);
|
||||||
|
|
||||||
buffer(buffer&& rhs);
|
buffer(buffer&& rhs);
|
||||||
~buffer();
|
~buffer();
|
||||||
|
|||||||
@ -26,6 +26,9 @@ public:
|
|||||||
bool open(char const *name) noexcept;
|
bool open(char const *name) noexcept;
|
||||||
void close() 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 wait(ipc::sync::mutex &mtx, std::uint64_t tm = ipc::invalid_value) noexcept;
|
||||||
bool notify(ipc::sync::mutex &mtx) noexcept;
|
bool notify(ipc::sync::mutex &mtx) noexcept;
|
||||||
bool broadcast(ipc::sync::mutex &mtx) noexcept;
|
bool broadcast(ipc::sync::mutex &mtx) noexcept;
|
||||||
|
|||||||
@ -19,7 +19,7 @@ enum : unsigned {
|
|||||||
|
|
||||||
template <typename Flag>
|
template <typename Flag>
|
||||||
struct IPC_EXPORT chan_impl {
|
struct IPC_EXPORT chan_impl {
|
||||||
static ipc::handle_t inited();
|
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 connect (ipc::handle_t * ph, prefix, char const * name, unsigned mode);
|
||||||
@ -29,8 +29,16 @@ struct IPC_EXPORT chan_impl {
|
|||||||
|
|
||||||
static char const * name(ipc::handle_t h);
|
static char const * name(ipc::handle_t h);
|
||||||
|
|
||||||
static std::size_t recv_count(ipc::handle_t h);
|
// Release memory without waiting for the connection to disconnect.
|
||||||
static bool wait_for_recv(ipc::handle_t h, std::size_t r_count, std::uint64_t tm);
|
static void release(ipc::handle_t h) noexcept;
|
||||||
|
|
||||||
|
// Force cleanup of all shared memory storage that handles depend on.
|
||||||
|
static void clear(ipc::handle_t h) noexcept;
|
||||||
|
static void clear_storage(char const * name) noexcept;
|
||||||
|
static void clear_storage(prefix, char const * name) noexcept;
|
||||||
|
|
||||||
|
static std::size_t recv_count (ipc::handle_t h);
|
||||||
|
static bool wait_for_recv(ipc::handle_t h, std::size_t r_count, std::uint64_t tm);
|
||||||
|
|
||||||
static bool send(ipc::handle_t h, void const * data, std::size_t size, std::uint64_t tm);
|
static bool send(ipc::handle_t h, void const * data, std::size_t size, std::uint64_t tm);
|
||||||
static buff_t recv(ipc::handle_t h, std::uint64_t tm);
|
static buff_t recv(ipc::handle_t h, std::uint64_t tm);
|
||||||
@ -44,7 +52,7 @@ class chan_wrapper {
|
|||||||
private:
|
private:
|
||||||
using detail_t = chan_impl<Flag>;
|
using detail_t = chan_impl<Flag>;
|
||||||
|
|
||||||
ipc::handle_t h_ = detail_t::inited();
|
ipc::handle_t h_ = detail_t::init_first();
|
||||||
unsigned mode_ = ipc::sender;
|
unsigned mode_ = ipc::sender;
|
||||||
bool connected_ = false;
|
bool connected_ = false;
|
||||||
|
|
||||||
@ -83,6 +91,28 @@ 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_;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,9 @@ public:
|
|||||||
bool open(char const *name) noexcept;
|
bool open(char const *name) noexcept;
|
||||||
void close() 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 lock(std::uint64_t tm = ipc::invalid_value) noexcept;
|
||||||
bool try_lock() noexcept(false); // std::system_error
|
bool try_lock() noexcept(false); // std::system_error
|
||||||
bool unlock() noexcept;
|
bool unlock() noexcept;
|
||||||
|
|||||||
@ -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);
|
static void* alloc(std::size_t size) noexcept;
|
||||||
static void free (void* p, std::size_t size);
|
static void free (void* p, std::size_t size) noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
#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.
|
||||||
@ -98,7 +99,7 @@ inline void sleep(K& k) {
|
|||||||
namespace ipc {
|
namespace ipc {
|
||||||
|
|
||||||
class spin_lock {
|
class spin_lock {
|
||||||
std::atomic<unsigned> lc_ { 0 };
|
std::atomic<std::uint32_t> lc_ { 0 };
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void lock(void) noexcept {
|
void lock(void) noexcept {
|
||||||
@ -113,13 +114,13 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
class rw_lock {
|
class rw_lock {
|
||||||
using lc_ui_t = unsigned;
|
using lc_ui_t = std::uint32_t;
|
||||||
|
|
||||||
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:
|
||||||
|
|||||||
@ -25,6 +25,9 @@ public:
|
|||||||
bool open(char const *name, std::uint32_t count = 0) noexcept;
|
bool open(char const *name, std::uint32_t count = 0) noexcept;
|
||||||
void close() 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 wait(std::uint64_t tm = ipc::invalid_value) noexcept;
|
||||||
bool post(std::uint32_t count = 1) noexcept;
|
bool post(std::uint32_t count = 1) noexcept;
|
||||||
|
|
||||||
|
|||||||
@ -17,9 +17,31 @@ enum : unsigned {
|
|||||||
|
|
||||||
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 std::int32_t release(id_t id);
|
|
||||||
IPC_EXPORT void remove (id_t id);
|
// Release shared memory resource and clean up disk file if reference count reaches zero.
|
||||||
IPC_EXPORT void remove (char const * name);
|
// This function decrements the reference counter. When the counter reaches zero, it:
|
||||||
|
// 1. Unmaps the shared memory region
|
||||||
|
// 2. Removes the backing file from disk (shm_unlink on POSIX)
|
||||||
|
// 3. Frees the id structure
|
||||||
|
// After calling this function, the id becomes invalid and must not be used again.
|
||||||
|
// Returns: The reference count before decrement, or -1 on error.
|
||||||
|
IPC_EXPORT std::int32_t release(id_t id) noexcept;
|
||||||
|
|
||||||
|
// Release shared memory resource and force cleanup of disk file.
|
||||||
|
// This function calls release(id) internally, then unconditionally attempts to
|
||||||
|
// remove the backing file. WARNING: Do NOT call this after release(id) on the
|
||||||
|
// same id, as the id is already freed by release(). Use this function alone,
|
||||||
|
// not in combination with release().
|
||||||
|
// Typical use case: Force cleanup when you want to ensure the disk file is removed
|
||||||
|
// regardless of reference count state.
|
||||||
|
IPC_EXPORT void remove (id_t id) noexcept;
|
||||||
|
|
||||||
|
// Remove shared memory backing file by name.
|
||||||
|
// This function only removes the disk file and does not affect any active memory
|
||||||
|
// mappings or id structures. Use this for cleanup of orphaned files or for explicit
|
||||||
|
// file removal without affecting runtime resources.
|
||||||
|
// Safe to call at any time, even if shared memory is still in use elsewhere.
|
||||||
|
IPC_EXPORT void remove (char const * name) noexcept;
|
||||||
|
|
||||||
IPC_EXPORT std::int32_t get_ref(id_t id);
|
IPC_EXPORT std::int32_t get_ref(id_t id);
|
||||||
IPC_EXPORT void sub_ref(id_t id);
|
IPC_EXPORT void sub_ref(id_t id);
|
||||||
@ -45,6 +67,10 @@ public:
|
|||||||
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();
|
std::int32_t release();
|
||||||
|
|
||||||
|
// Clean the handle file.
|
||||||
|
void clear() noexcept;
|
||||||
|
static void clear_storage(char const * name) noexcept;
|
||||||
|
|
||||||
void* get() const;
|
void* get() const;
|
||||||
|
|
||||||
void attach(id_t);
|
void attach(id_t);
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
project(ipc)
|
project(ipc)
|
||||||
|
|
||||||
|
set (PACKAGE_VERSION 1.3.0)
|
||||||
|
|
||||||
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc SRC_FILES)
|
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc SRC_FILES)
|
||||||
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/sync SRC_FILES)
|
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/sync SRC_FILES)
|
||||||
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/platform SRC_FILES)
|
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/platform SRC_FILES)
|
||||||
@ -34,11 +36,13 @@ set_target_properties(${PROJECT_NAME}
|
|||||||
# set version
|
# set version
|
||||||
set_target_properties(${PROJECT_NAME}
|
set_target_properties(${PROJECT_NAME}
|
||||||
PROPERTIES
|
PROPERTIES
|
||||||
VERSION 1.2.0
|
VERSION ${PACKAGE_VERSION}
|
||||||
SOVERSION 3)
|
SOVERSION 3)
|
||||||
|
|
||||||
target_include_directories(${PROJECT_NAME}
|
target_include_directories(${PROJECT_NAME}
|
||||||
PUBLIC ${LIBIPC_PROJECT_DIR}/include
|
PUBLIC
|
||||||
|
"$<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>)
|
$<$<BOOL:UNIX>:${LIBIPC_PROJECT_DIR}/src/libipc/platform/linux>)
|
||||||
|
|
||||||
@ -50,6 +54,28 @@ 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* additional)
|
buffer::buffer(void* p, std::size_t s, destructor_t d, void* mem_to_free)
|
||||||
: p_(p_->make(p, s, d, additional)) {
|
: p_(p_->make(p, s, d, mem_to_free)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 const & c)
|
buffer::buffer(char & c)
|
||||||
: buffer(const_cast<char*>(&c), 1) {
|
: buffer(&c, 1) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer::buffer(buffer&& rhs)
|
buffer::buffer(buffer&& rhs)
|
||||||
|
|||||||
@ -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 == 0) {
|
if (next == curr) {
|
||||||
// connection-slot is full.
|
// connection-slot is full.
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -74,6 +74,10 @@ 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
|
||||||
@ -100,6 +104,11 @@ 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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -136,6 +136,22 @@ struct conn_info_head {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
void quit_waiting() {
|
||||||
cc_waiter_.quit_waiting();
|
cc_waiter_.quit_waiting();
|
||||||
wt_waiter_.quit_waiting();
|
wt_waiter_.quit_waiting();
|
||||||
@ -379,13 +395,27 @@ struct queue_generator {
|
|||||||
conn_info_head::init();
|
conn_info_head::init();
|
||||||
if (!que_.valid()) {
|
if (!que_.valid()) {
|
||||||
que_.open(ipc::make_prefix(prefix_, {
|
que_.open(ipc::make_prefix(prefix_, {
|
||||||
"QU_CONN__",
|
"QU_CONN__",
|
||||||
ipc::to_string(DataSize), "__",
|
this->name_,
|
||||||
ipc::to_string(AlignSize), "__",
|
"__", ipc::to_string(DataSize),
|
||||||
this->name_}).c_str());
|
"__", 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() {
|
||||||
bool dis = que_.disconnect();
|
bool dis = que_.disconnect();
|
||||||
this->quit_waiting();
|
this->quit_waiting();
|
||||||
@ -459,8 +489,7 @@ 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) {
|
static void destroy(ipc::handle_t h) noexcept {
|
||||||
disconnect(h);
|
|
||||||
ipc::mem::free(info_of(h));
|
ipc::mem::free(info_of(h));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -598,7 +627,10 @@ static ipc::buff_t recv(ipc::handle_t h, std::uint64_t tm) {
|
|||||||
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] {
|
if (!wait_for(inf->rd_waiter_, [que, &msg, &h] {
|
||||||
|
if (!que->connected()) {
|
||||||
|
reconnect(&h, true);
|
||||||
|
}
|
||||||
return !que->pop(msg);
|
return !que->pop(msg);
|
||||||
}, tm)) {
|
}, tm)) {
|
||||||
// pop failed, just return.
|
// pop failed, just return.
|
||||||
@ -703,7 +735,7 @@ using policy_t = ipc::policy::choose<ipc::circ::elem_array, Flag>;
|
|||||||
namespace ipc {
|
namespace ipc {
|
||||||
|
|
||||||
template <typename Flag>
|
template <typename Flag>
|
||||||
ipc::handle_t chan_impl<Flag>::inited() {
|
ipc::handle_t chan_impl<Flag>::init_first() {
|
||||||
ipc::detail::waiter::init();
|
ipc::detail::waiter::init();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@ -730,6 +762,12 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -739,6 +777,27 @@ char const * chan_impl<Flag>::name(ipc::handle_t 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);
|
||||||
|
|||||||
@ -19,17 +19,17 @@ namespace mem {
|
|||||||
|
|
||||||
class static_alloc {
|
class static_alloc {
|
||||||
public:
|
public:
|
||||||
static void swap(static_alloc&) {}
|
static void swap(static_alloc&) noexcept {}
|
||||||
|
|
||||||
static void* alloc(std::size_t size) {
|
static void* alloc(std::size_t size) noexcept {
|
||||||
return size ? std::malloc(size) : nullptr;
|
return size ? std::malloc(size) : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void free(void* p) {
|
static void free(void* p) noexcept {
|
||||||
std::free(p);
|
std::free(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void free(void* p, std::size_t /*size*/) {
|
static void free(void* p, std::size_t /*size*/) noexcept {
|
||||||
free(p);
|
free(p);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,6 +9,8 @@
|
|||||||
# define IPC_OS_WINDOWS_
|
# define IPC_OS_WINDOWS_
|
||||||
#elif defined(__linux__) || defined(__linux)
|
#elif defined(__linux__) || defined(__linux)
|
||||||
# define IPC_OS_LINUX_
|
# define IPC_OS_LINUX_
|
||||||
|
#elif defined(__FreeBSD__)
|
||||||
|
# define IPC_OS_FREEBSD_
|
||||||
#elif defined(__QNX__)
|
#elif defined(__QNX__)
|
||||||
# define IPC_OS_QNX_
|
# define IPC_OS_QNX_
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
@ -70,15 +72,9 @@
|
|||||||
|
|
||||||
#if __cplusplus >= 201703L
|
#if __cplusplus >= 201703L
|
||||||
|
|
||||||
namespace std {
|
|
||||||
|
|
||||||
// deduction guides for std::unique_ptr
|
// C++17 and later: std library already provides deduction guides
|
||||||
template <typename T>
|
// No need to add custom ones, just use the standard ones directly
|
||||||
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 {
|
||||||
|
|||||||
@ -27,7 +27,7 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
auto ts = detail::make_timespec(tm);
|
auto ts = linux_::detail::make_timespec(tm);
|
||||||
int eno = A0_SYSERR(a0_cnd_timedwait(native(), static_cast<a0_mtx_t *>(mtx.native()), {ts}));
|
int eno = A0_SYSERR(a0_cnd_timedwait(native(), static_cast<a0_mtx_t *>(mtx.native()), {ts}));
|
||||||
if (eno != 0) {
|
if (eno != 0) {
|
||||||
if (eno != ETIMEDOUT) {
|
if (eno != ETIMEDOUT) {
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
#include "a0/err_macro.h"
|
#include "a0/err_macro.h"
|
||||||
|
|
||||||
namespace ipc {
|
namespace ipc {
|
||||||
|
namespace linux_ {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
inline bool calc_wait_time(timespec &ts, std::uint64_t tm /*ms*/) noexcept {
|
inline bool calc_wait_time(timespec &ts, std::uint64_t tm /*ms*/) noexcept {
|
||||||
@ -43,4 +44,5 @@ inline timespec make_timespec(std::uint64_t tm /*ms*/) noexcept(false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
} // namespace linux_
|
||||||
} // namespace ipc
|
} // namespace ipc
|
||||||
|
|||||||
@ -25,7 +25,7 @@ public:
|
|||||||
bool lock(std::uint64_t tm) noexcept {
|
bool lock(std::uint64_t tm) noexcept {
|
||||||
if (!valid()) return false;
|
if (!valid()) return false;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
auto ts = detail::make_timespec(tm);
|
auto ts = linux_::detail::make_timespec(tm);
|
||||||
int eno = A0_SYSERR(
|
int eno = A0_SYSERR(
|
||||||
(tm == invalid_value) ? a0_mtx_lock(native())
|
(tm == invalid_value) ? a0_mtx_lock(native())
|
||||||
: a0_mtx_timedlock(native(), {ts}));
|
: a0_mtx_timedlock(native(), {ts}));
|
||||||
@ -56,7 +56,7 @@ public:
|
|||||||
|
|
||||||
bool try_lock() noexcept(false) {
|
bool try_lock() noexcept(false) {
|
||||||
if (!valid()) return false;
|
if (!valid()) return false;
|
||||||
int eno = A0_SYSERR(a0_mtx_timedlock(native(), {detail::make_timespec(0)}));
|
int eno = A0_SYSERR(a0_mtx_timedlock(native(), {linux_::detail::make_timespec(0)}));
|
||||||
switch (eno) {
|
switch (eno) {
|
||||||
case 0:
|
case 0:
|
||||||
return true;
|
return true;
|
||||||
@ -125,15 +125,18 @@ class mutex {
|
|||||||
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
|
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
|
||||||
auto it = info.mutex_handles.find(name);
|
auto it = info.mutex_handles.find(name);
|
||||||
if (it == info.mutex_handles.end()) {
|
if (it == info.mutex_handles.end()) {
|
||||||
it = info.mutex_handles.emplace(name,
|
it = info.mutex_handles
|
||||||
curr_prog::shm_data::init{name}).first;
|
.emplace(std::piecewise_construct,
|
||||||
|
std::forward_as_tuple(name),
|
||||||
|
std::forward_as_tuple(curr_prog::shm_data::init{name}))
|
||||||
|
.first;
|
||||||
}
|
}
|
||||||
mutex_ = &it->second.mtx;
|
mutex_ = &it->second.mtx;
|
||||||
ref_ = &it->second.ref;
|
ref_ = &it->second.ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename F>
|
template <typename F>
|
||||||
void release_mutex(ipc::string const &name, F &&clear) {
|
static void release_mutex(ipc::string const &name, F &&clear) {
|
||||||
if (name.empty()) return;
|
if (name.empty()) return;
|
||||||
auto &info = curr_prog::get();
|
auto &info = curr_prog::get();
|
||||||
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
|
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
|
||||||
@ -189,6 +192,25 @@ public:
|
|||||||
ref_ = 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 {
|
bool lock(std::uint64_t tm) noexcept {
|
||||||
if (!valid()) return false;
|
if (!valid()) return false;
|
||||||
return mutex_->lock(tm);
|
return mutex_->lock(tm);
|
||||||
|
|||||||
@ -62,6 +62,15 @@ public:
|
|||||||
shm_.release();
|
shm_.release();
|
||||||
h_ = nullptr;
|
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 sync
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
#include "libipc/platform/linux/a0/strconv.c"
|
#include "libipc/platform/linux/a0/strconv.c"
|
||||||
#include "libipc/platform/linux/a0/tid.c"
|
#include "libipc/platform/linux/a0/tid.c"
|
||||||
#include "libipc/platform/linux/a0/time.c"
|
#include "libipc/platform/linux/a0/time.c"
|
||||||
#elif defined(IPC_OS_QNX_)
|
#elif defined(IPC_OS_QNX_) || defined(IPC_OS_FREEBSD_)
|
||||||
#else/*IPC_OS*/
|
#else/*IPC_OS*/
|
||||||
# error "Unsupported platform."
|
# error "Unsupported platform."
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
#include "libipc/platform/detail.h"
|
#include "libipc/platform/detail.h"
|
||||||
#if defined(IPC_OS_WINDOWS_)
|
#if defined(IPC_OS_WINDOWS_)
|
||||||
#include "libipc/platform/win/shm_win.cpp"
|
#include "libipc/platform/win/shm_win.cpp"
|
||||||
#elif defined(IPC_OS_LINUX_) || defined(IPC_OS_QNX_)
|
#elif defined(IPC_OS_LINUX_) || defined(IPC_OS_QNX_) || defined(IPC_OS_FREEBSD_)
|
||||||
#include "libipc/platform/posix/shm_posix.cpp"
|
#include "libipc/platform/posix/shm_posix.cpp"
|
||||||
#else/*IPC_OS*/
|
#else/*IPC_OS*/
|
||||||
# error "Unsupported platform."
|
# error "Unsupported platform."
|
||||||
|
|||||||
@ -88,6 +88,21 @@ public:
|
|||||||
cond_ = nullptr;
|
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 {
|
bool wait(ipc::sync::mutex &mtx, std::uint64_t tm) noexcept {
|
||||||
if (!valid()) return false;
|
if (!valid()) return false;
|
||||||
switch (tm) {
|
switch (tm) {
|
||||||
@ -100,7 +115,7 @@ public:
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default: {
|
default: {
|
||||||
auto ts = detail::make_timespec(tm);
|
auto ts = posix_::detail::make_timespec(tm);
|
||||||
int eno;
|
int eno;
|
||||||
if ((eno = ::pthread_cond_timedwait(cond_, static_cast<pthread_mutex_t *>(mtx.native()), &ts)) != 0) {
|
if ((eno = ::pthread_cond_timedwait(cond_, static_cast<pthread_mutex_t *>(mtx.native()), &ts)) != 0) {
|
||||||
if (eno != ETIMEDOUT) {
|
if (eno != ETIMEDOUT) {
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
#include "libipc/utility/log.h"
|
#include "libipc/utility/log.h"
|
||||||
|
|
||||||
namespace ipc {
|
namespace ipc {
|
||||||
|
namespace posix_ {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
inline bool calc_wait_time(timespec &ts, std::uint64_t tm /*ms*/) noexcept {
|
inline bool calc_wait_time(timespec &ts, std::uint64_t tm /*ms*/) noexcept {
|
||||||
@ -36,4 +37,5 @@ inline timespec make_timespec(std::uint64_t tm /*ms*/) noexcept(false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
} // namespace posix_
|
||||||
} // namespace ipc
|
} // namespace ipc
|
||||||
|
|||||||
@ -55,8 +55,12 @@ class mutex {
|
|||||||
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
|
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
|
||||||
auto it = info.mutex_handles.find(name);
|
auto it = info.mutex_handles.find(name);
|
||||||
if (it == info.mutex_handles.end()) {
|
if (it == info.mutex_handles.end()) {
|
||||||
it = info.mutex_handles.emplace(name,
|
it = info.mutex_handles
|
||||||
curr_prog::shm_data::init{name, sizeof(pthread_mutex_t)}).first;
|
.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;
|
shm_ = &it->second.shm;
|
||||||
ref_ = &it->second.ref;
|
ref_ = &it->second.ref;
|
||||||
@ -67,7 +71,7 @@ class mutex {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename F>
|
template <typename F>
|
||||||
void release_mutex(ipc::string const &name, F &&clear) {
|
static void release_mutex(ipc::string const &name, F &&clear) {
|
||||||
if (name.empty()) return;
|
if (name.empty()) return;
|
||||||
auto &info = curr_prog::get();
|
auto &info = curr_prog::get();
|
||||||
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
|
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
|
||||||
@ -150,6 +154,15 @@ public:
|
|||||||
release_mutex(shm_->name(), [this] {
|
release_mutex(shm_->name(), [this] {
|
||||||
auto self_ref = ref_->fetch_sub(1, std::memory_order_relaxed);
|
auto self_ref = ref_->fetch_sub(1, std::memory_order_relaxed);
|
||||||
if ((shm_->ref() <= 1) && (self_ref <= 1)) {
|
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;
|
int eno;
|
||||||
if ((eno = ::pthread_mutex_destroy(mutex_)) != 0) {
|
if ((eno = ::pthread_mutex_destroy(mutex_)) != 0) {
|
||||||
ipc::error("fail pthread_mutex_destroy[%d]\n", eno);
|
ipc::error("fail pthread_mutex_destroy[%d]\n", eno);
|
||||||
@ -165,10 +178,37 @@ public:
|
|||||||
mutex_ = 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 {
|
bool lock(std::uint64_t tm) noexcept {
|
||||||
if (!valid()) return false;
|
if (!valid()) return false;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
auto ts = detail::make_timespec(tm);
|
auto ts = posix_::detail::make_timespec(tm);
|
||||||
int eno = (tm == invalid_value)
|
int eno = (tm == invalid_value)
|
||||||
? ::pthread_mutex_lock(mutex_)
|
? ::pthread_mutex_lock(mutex_)
|
||||||
: ::pthread_mutex_timedlock(mutex_, &ts);
|
: ::pthread_mutex_timedlock(mutex_, &ts);
|
||||||
@ -178,21 +218,17 @@ public:
|
|||||||
case ETIMEDOUT:
|
case ETIMEDOUT:
|
||||||
return false;
|
return false;
|
||||||
case EOWNERDEAD: {
|
case EOWNERDEAD: {
|
||||||
if (shm_->ref() > 1) {
|
// EOWNERDEAD means we have successfully acquired the lock,
|
||||||
shm_->sub_ref();
|
// but the previous owner died. We need to make it consistent.
|
||||||
}
|
|
||||||
int eno2 = ::pthread_mutex_consistent(mutex_);
|
int eno2 = ::pthread_mutex_consistent(mutex_);
|
||||||
if (eno2 != 0) {
|
if (eno2 != 0) {
|
||||||
ipc::error("fail pthread_mutex_lock[%d], pthread_mutex_consistent[%d]\n", eno, eno2);
|
ipc::error("fail pthread_mutex_lock[%d], pthread_mutex_consistent[%d]\n", eno, eno2);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int eno3 = ::pthread_mutex_unlock(mutex_);
|
// After calling pthread_mutex_consistent(), the mutex is now in a
|
||||||
if (eno3 != 0) {
|
// consistent state and we hold the lock. Return success.
|
||||||
ipc::error("fail pthread_mutex_lock[%d], pthread_mutex_unlock[%d]\n", eno, eno3);
|
return true;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break; // loop again
|
|
||||||
default:
|
default:
|
||||||
ipc::error("fail pthread_mutex_lock[%d]\n", eno);
|
ipc::error("fail pthread_mutex_lock[%d]\n", eno);
|
||||||
return false;
|
return false;
|
||||||
@ -202,7 +238,7 @@ public:
|
|||||||
|
|
||||||
bool try_lock() noexcept(false) {
|
bool try_lock() noexcept(false) {
|
||||||
if (!valid()) return false;
|
if (!valid()) return false;
|
||||||
auto ts = detail::make_timespec(0);
|
auto ts = posix_::detail::make_timespec(0);
|
||||||
int eno = ::pthread_mutex_timedlock(mutex_, &ts);
|
int eno = ::pthread_mutex_timedlock(mutex_, &ts);
|
||||||
switch (eno) {
|
switch (eno) {
|
||||||
case 0:
|
case 0:
|
||||||
@ -210,21 +246,17 @@ public:
|
|||||||
case ETIMEDOUT:
|
case ETIMEDOUT:
|
||||||
return false;
|
return false;
|
||||||
case EOWNERDEAD: {
|
case EOWNERDEAD: {
|
||||||
if (shm_->ref() > 1) {
|
// EOWNERDEAD means we have successfully acquired the lock,
|
||||||
shm_->sub_ref();
|
// but the previous owner died. We need to make it consistent.
|
||||||
}
|
|
||||||
int eno2 = ::pthread_mutex_consistent(mutex_);
|
int eno2 = ::pthread_mutex_consistent(mutex_);
|
||||||
if (eno2 != 0) {
|
if (eno2 != 0) {
|
||||||
ipc::error("fail pthread_mutex_timedlock[%d], pthread_mutex_consistent[%d]\n", eno, eno2);
|
ipc::error("fail pthread_mutex_timedlock[%d], pthread_mutex_consistent[%d]\n", eno, eno2);
|
||||||
break;
|
throw std::system_error{eno2, std::system_category()};
|
||||||
}
|
|
||||||
int eno3 = ::pthread_mutex_unlock(mutex_);
|
|
||||||
if (eno3 != 0) {
|
|
||||||
ipc::error("fail pthread_mutex_timedlock[%d], pthread_mutex_unlock[%d]\n", eno, eno3);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
// After calling pthread_mutex_consistent(), the mutex is now in a
|
||||||
|
// consistent state and we hold the lock. Return success.
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
ipc::error("fail pthread_mutex_timedlock[%d]\n", eno);
|
ipc::error("fail pthread_mutex_timedlock[%d]\n", eno);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -19,6 +19,7 @@ namespace sync {
|
|||||||
class semaphore {
|
class semaphore {
|
||||||
ipc::shm::handle shm_;
|
ipc::shm::handle shm_;
|
||||||
sem_t *h_ = SEM_FAILED;
|
sem_t *h_ = SEM_FAILED;
|
||||||
|
std::string sem_name_; // Store the actual semaphore name used
|
||||||
|
|
||||||
public:
|
public:
|
||||||
semaphore() = default;
|
semaphore() = default;
|
||||||
@ -38,9 +39,16 @@ public:
|
|||||||
ipc::error("[open_semaphore] fail shm.acquire: %s\n", name);
|
ipc::error("[open_semaphore] fail shm.acquire: %s\n", name);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
h_ = ::sem_open(name, O_CREAT, 0666, static_cast<unsigned>(count));
|
// 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) {
|
if (h_ == SEM_FAILED) {
|
||||||
ipc::error("fail sem_open[%d]: %s\n", errno, name);
|
ipc::error("fail sem_open[%d]: %s\n", errno, sem_name_.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -52,14 +60,40 @@ public:
|
|||||||
ipc::error("fail sem_close[%d]: %s\n", errno);
|
ipc::error("fail sem_close[%d]: %s\n", errno);
|
||||||
}
|
}
|
||||||
h_ = SEM_FAILED;
|
h_ = SEM_FAILED;
|
||||||
if (shm_.name() != nullptr) {
|
if (!sem_name_.empty() && shm_.name() != nullptr) {
|
||||||
std::string name = shm_.name();
|
|
||||||
if (shm_.release() <= 1) {
|
if (shm_.release() <= 1) {
|
||||||
if (::sem_unlink(name.c_str()) != 0) {
|
if (::sem_unlink(sem_name_.c_str()) != 0) {
|
||||||
ipc::error("fail sem_unlink[%d]: %s, name: %s\n", errno, name.c_str());
|
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 {
|
bool wait(std::uint64_t tm) noexcept {
|
||||||
@ -70,7 +104,7 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
auto ts = detail::make_timespec(tm);
|
auto ts = posix_::detail::make_timespec(tm);
|
||||||
if (::sem_timedwait(h_, &ts) != 0) {
|
if (::sem_timedwait(h_, &ts) != 0) {
|
||||||
if (errno != ETIMEDOUT) {
|
if (errno != ETIMEDOUT) {
|
||||||
ipc::error("fail sem_timedwait[%d]: tm = %zd, tv_sec = %ld, tv_nsec = %ld\n",
|
ipc::error("fail sem_timedwait[%d]: tm = %zd, tv_sec = %ld, tv_nsec = %ld\n",
|
||||||
|
|||||||
@ -51,7 +51,12 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
|
|||||||
}
|
}
|
||||||
// For portable use, a shared memory object should be identified by name of the form /somename.
|
// 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
|
// see: https://man7.org/linux/man-pages/man3/shm_open.3.html
|
||||||
ipc::string op_name = ipc::string{"/"} + name;
|
ipc::string op_name;
|
||||||
|
if (name[0] == '/') {
|
||||||
|
op_name = name;
|
||||||
|
} else {
|
||||||
|
op_name = ipc::string{"/"} + name;
|
||||||
|
}
|
||||||
// Open the object for read-write access.
|
// Open the object for read-write access.
|
||||||
int flag = O_RDWR;
|
int flag = O_RDWR;
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
@ -68,13 +73,19 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
|
|||||||
flag |= O_CREAT;
|
flag |= O_CREAT;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
int fd = ::shm_open(op_name.c_str(), flag, S_IRUSR | S_IWUSR |
|
int fd = ::shm_open(op_name.c_str(), flag, S_IRUSR | S_IWUSR |
|
||||||
S_IRGRP | S_IWGRP |
|
S_IRGRP | S_IWGRP |
|
||||||
S_IROTH | S_IWOTH);
|
S_IROTH | S_IWOTH);
|
||||||
if (fd == -1) {
|
if (fd == -1) {
|
||||||
ipc::error("fail shm_open[%d]: %s\n", errno, op_name.c_str());
|
// only open shm not log error when file not exist
|
||||||
|
if (open != mode || ENOENT != errno) {
|
||||||
|
ipc::error("fail shm_open[%d]: %s\n", errno, op_name.c_str());
|
||||||
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
::fchmod(fd, S_IRUSR | S_IWUSR |
|
||||||
|
S_IRGRP | S_IWGRP |
|
||||||
|
S_IROTH | S_IWOTH);
|
||||||
auto ii = mem::alloc<id_info_t>();
|
auto ii = mem::alloc<id_info_t>();
|
||||||
ii->fd_ = fd;
|
ii->fd_ = fd;
|
||||||
ii->size_ = size;
|
ii->size_ = size;
|
||||||
@ -153,7 +164,7 @@ void * get_mem(id_t id, std::size_t * size) {
|
|||||||
return mem;
|
return mem;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::int32_t release(id_t id) {
|
std::int32_t release(id_t id) noexcept {
|
||||||
if (id == nullptr) {
|
if (id == nullptr) {
|
||||||
ipc::error("fail release: invalid id (null)\n");
|
ipc::error("fail release: invalid id (null)\n");
|
||||||
return -1;
|
return -1;
|
||||||
@ -167,7 +178,10 @@ std::int32_t release(id_t id) {
|
|||||||
else if ((ret = acc_of(ii->mem_, ii->size_).fetch_sub(1, std::memory_order_acq_rel)) <= 1) {
|
else if ((ret = acc_of(ii->mem_, ii->size_).fetch_sub(1, std::memory_order_acq_rel)) <= 1) {
|
||||||
::munmap(ii->mem_, ii->size_);
|
::munmap(ii->mem_, ii->size_);
|
||||||
if (!ii->name_.empty()) {
|
if (!ii->name_.empty()) {
|
||||||
::shm_unlink(ii->name_.c_str());
|
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_);
|
else ::munmap(ii->mem_, ii->size_);
|
||||||
@ -175,7 +189,7 @@ std::int32_t release(id_t id) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove(id_t id) {
|
void remove(id_t id) noexcept {
|
||||||
if (id == nullptr) {
|
if (id == nullptr) {
|
||||||
ipc::error("fail remove: invalid id (null)\n");
|
ipc::error("fail remove: invalid id (null)\n");
|
||||||
return;
|
return;
|
||||||
@ -184,16 +198,29 @@ void remove(id_t id) {
|
|||||||
auto name = std::move(ii->name_);
|
auto name = std::move(ii->name_);
|
||||||
release(id);
|
release(id);
|
||||||
if (!name.empty()) {
|
if (!name.empty()) {
|
||||||
::shm_unlink(name.c_str());
|
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) {
|
void remove(char const * name) noexcept {
|
||||||
if (!is_valid_string(name)) {
|
if (!is_valid_string(name)) {
|
||||||
ipc::error("fail remove: name is empty\n");
|
ipc::error("fail remove: name is empty\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
::shm_unlink(name);
|
// 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 shm
|
||||||
|
|||||||
@ -4,7 +4,11 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
|
#if defined(__MINGW32__)
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "libipc/utility/log.h"
|
#include "libipc/utility/log.h"
|
||||||
#include "libipc/utility/scope_guard.h"
|
#include "libipc/utility/scope_guard.h"
|
||||||
@ -67,6 +71,16 @@ public:
|
|||||||
shm_.release();
|
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 {
|
bool wait(ipc::sync::mutex &mtx, std::uint64_t tm) noexcept {
|
||||||
if (!valid()) return false;
|
if (!valid()) return false;
|
||||||
auto &cnt = counter();
|
auto &cnt = counter();
|
||||||
|
|||||||
@ -3,7 +3,11 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <system_error>
|
#include <system_error>
|
||||||
|
|
||||||
|
#if defined(__MINGW32__)
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "libipc/utility/log.h"
|
#include "libipc/utility/log.h"
|
||||||
|
|
||||||
@ -47,6 +51,13 @@ public:
|
|||||||
h_ = NULL;
|
h_ = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void clear() noexcept {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clear_storage(char const */*name*/) noexcept {
|
||||||
|
}
|
||||||
|
|
||||||
bool lock(std::uint64_t tm) noexcept {
|
bool lock(std::uint64_t tm) noexcept {
|
||||||
DWORD ret, ms = (tm == invalid_value) ? INFINITE : static_cast<DWORD>(tm);
|
DWORD ret, ms = (tm == invalid_value) ? INFINITE : static_cast<DWORD>(tm);
|
||||||
for(;;) {
|
for(;;) {
|
||||||
|
|||||||
@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
#if defined(__MINGW32__)
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "libipc/utility/log.h"
|
#include "libipc/utility/log.h"
|
||||||
|
|
||||||
@ -46,6 +50,13 @@ public:
|
|||||||
h_ = NULL;
|
h_ = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void clear() noexcept {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clear_storage(char const */*name*/) noexcept {
|
||||||
|
}
|
||||||
|
|
||||||
bool wait(std::uint64_t tm) noexcept {
|
bool wait(std::uint64_t tm) noexcept {
|
||||||
DWORD ret, ms = (tm == invalid_value) ? INFINITE : static_cast<DWORD>(tm);
|
DWORD ret, ms = (tm == invalid_value) ? INFINITE : static_cast<DWORD>(tm);
|
||||||
switch ((ret = ::WaitForSingleObject(h_, ms))) {
|
switch ((ret = ::WaitForSingleObject(h_, ms))) {
|
||||||
|
|||||||
@ -1,140 +1,185 @@
|
|||||||
|
|
||||||
#include <Windows.h>
|
#if defined(__MINGW32__)
|
||||||
|
#include <windows.h>
|
||||||
#include <string>
|
#else
|
||||||
#include <utility>
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
#include "libipc/shm.h"
|
|
||||||
#include "libipc/def.h"
|
#include <atomic>
|
||||||
#include "libipc/pool_alloc.h"
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
#include "libipc/utility/log.h"
|
|
||||||
#include "libipc/memory/resource.h"
|
#include "libipc/shm.h"
|
||||||
|
#include "libipc/def.h"
|
||||||
#include "to_tchar.h"
|
#include "libipc/pool_alloc.h"
|
||||||
#include "get_sa.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 (!is_valid_string(name)) {
|
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(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_;
|
||||||
if (h == NULL) {
|
}
|
||||||
ipc::error("fail OpenFileMapping[%d]: %s\n", static_cast<int>(::GetLastError()), name);
|
|
||||||
return nullptr;
|
} // internal-linkage
|
||||||
}
|
|
||||||
}
|
namespace ipc {
|
||||||
// Creates or opens a named file mapping object for a specified file.
|
namespace shm {
|
||||||
else {
|
|
||||||
h = ::CreateFileMapping(INVALID_HANDLE_VALUE, detail::get_sa(), PAGE_READWRITE | SEC_COMMIT,
|
id_t acquire(char const * name, std::size_t size, unsigned mode) {
|
||||||
0, static_cast<DWORD>(size), fmt_name.c_str());
|
if (!is_valid_string(name)) {
|
||||||
DWORD err = ::GetLastError();
|
ipc::error("fail acquire: name is empty\n");
|
||||||
// If the object exists before the function call, the function returns a handle to the existing object
|
return nullptr;
|
||||||
// (with its current size, not the specified size), and GetLastError returns ERROR_ALREADY_EXISTS.
|
}
|
||||||
if ((mode == create) && (err == ERROR_ALREADY_EXISTS)) {
|
HANDLE h;
|
||||||
if (h != NULL) ::CloseHandle(h);
|
auto fmt_name = ipc::detail::to_tchar(name);
|
||||||
h = NULL;
|
// Opens a named file mapping object.
|
||||||
}
|
if (mode == open) {
|
||||||
if (h == NULL) {
|
h = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, fmt_name.c_str());
|
||||||
ipc::error("fail CreateFileMapping[%d]: %s\n", static_cast<int>(err), name);
|
if (h == NULL) {
|
||||||
return nullptr;
|
ipc::error("fail OpenFileMapping[%d]: %s\n", static_cast<int>(::GetLastError()), name);
|
||||||
}
|
return nullptr;
|
||||||
}
|
}
|
||||||
auto ii = mem::alloc<id_info_t>();
|
}
|
||||||
ii->h_ = h;
|
// Creates or opens a named file mapping object for a specified file.
|
||||||
ii->size_ = size;
|
else {
|
||||||
return ii;
|
std::size_t alloc_size = calc_size(size);
|
||||||
}
|
h = ::CreateFileMapping(INVALID_HANDLE_VALUE, detail::get_sa(), PAGE_READWRITE | SEC_COMMIT,
|
||||||
|
0, static_cast<DWORD>(alloc_size), fmt_name.c_str());
|
||||||
std::int32_t get_ref(id_t) {
|
DWORD err = ::GetLastError();
|
||||||
return 0;
|
// If the object exists before the function call, the function returns a handle to the existing object
|
||||||
}
|
// (with its current size, not the specified size), and GetLastError returns ERROR_ALREADY_EXISTS.
|
||||||
|
if ((mode == create) && (err == ERROR_ALREADY_EXISTS)) {
|
||||||
void sub_ref(id_t) {
|
if (h != NULL) ::CloseHandle(h);
|
||||||
// Do Nothing.
|
h = NULL;
|
||||||
}
|
}
|
||||||
|
if (h == NULL) {
|
||||||
void * get_mem(id_t id, std::size_t * size) {
|
ipc::error("fail CreateFileMapping[%d]: %s\n", static_cast<int>(err), name);
|
||||||
if (id == nullptr) {
|
return nullptr;
|
||||||
ipc::error("fail get_mem: invalid id (null)\n");
|
}
|
||||||
return nullptr;
|
}
|
||||||
}
|
auto ii = mem::alloc<id_info_t>();
|
||||||
auto ii = static_cast<id_info_t*>(id);
|
ii->h_ = h;
|
||||||
if (ii->mem_ != nullptr) {
|
ii->size_ = size;
|
||||||
if (size != nullptr) *size = ii->size_;
|
return ii;
|
||||||
return ii->mem_;
|
}
|
||||||
}
|
|
||||||
if (ii->h_ == NULL) {
|
std::int32_t get_ref(id_t id) {
|
||||||
ipc::error("fail to_mem: invalid id (h = null)\n");
|
if (id == nullptr) {
|
||||||
return nullptr;
|
return 0;
|
||||||
}
|
}
|
||||||
LPVOID mem = ::MapViewOfFile(ii->h_, FILE_MAP_ALL_ACCESS, 0, 0, 0);
|
auto ii = static_cast<id_info_t*>(id);
|
||||||
if (mem == NULL) {
|
if (ii->mem_ == nullptr || ii->size_ == 0) {
|
||||||
ipc::error("fail MapViewOfFile[%d]\n", static_cast<int>(::GetLastError()));
|
return 0;
|
||||||
return nullptr;
|
}
|
||||||
}
|
return acc_of(ii->mem_, calc_size(ii->size_)).load(std::memory_order_acquire);
|
||||||
MEMORY_BASIC_INFORMATION mem_info;
|
}
|
||||||
if (::VirtualQuery(mem, &mem_info, sizeof(mem_info)) == 0) {
|
|
||||||
ipc::error("fail VirtualQuery[%d]\n", static_cast<int>(::GetLastError()));
|
void sub_ref(id_t id) {
|
||||||
return nullptr;
|
if (id == nullptr) {
|
||||||
}
|
ipc::error("fail sub_ref: invalid id (null)\n");
|
||||||
ii->mem_ = mem;
|
return;
|
||||||
ii->size_ = static_cast<std::size_t>(mem_info.RegionSize);
|
}
|
||||||
if (size != nullptr) *size = ii->size_;
|
auto ii = static_cast<id_info_t*>(id);
|
||||||
return static_cast<void *>(mem);
|
if (ii->mem_ == nullptr || ii->size_ == 0) {
|
||||||
}
|
ipc::error("fail sub_ref: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
|
||||||
|
return;
|
||||||
std::int32_t release(id_t id) {
|
}
|
||||||
if (id == nullptr) {
|
acc_of(ii->mem_, calc_size(ii->size_)).fetch_sub(1, std::memory_order_acq_rel);
|
||||||
ipc::error("fail release: invalid id (null)\n");
|
}
|
||||||
return -1;
|
|
||||||
}
|
void * get_mem(id_t id, std::size_t * size) {
|
||||||
auto ii = static_cast<id_info_t*>(id);
|
if (id == nullptr) {
|
||||||
if (ii->mem_ == nullptr || ii->size_ == 0) {
|
ipc::error("fail get_mem: invalid id (null)\n");
|
||||||
ipc::error("fail release: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
|
return nullptr;
|
||||||
}
|
}
|
||||||
else ::UnmapViewOfFile(static_cast<LPCVOID>(ii->mem_));
|
auto ii = static_cast<id_info_t*>(id);
|
||||||
if (ii->h_ == NULL) {
|
if (ii->mem_ != nullptr) {
|
||||||
ipc::error("fail release: invalid id (h = null)\n");
|
if (size != nullptr) *size = ii->size_;
|
||||||
}
|
return ii->mem_;
|
||||||
else ::CloseHandle(ii->h_);
|
}
|
||||||
mem::free(ii);
|
if (ii->h_ == NULL) {
|
||||||
return 0;
|
ipc::error("fail to_mem: invalid id (h = null)\n");
|
||||||
}
|
return nullptr;
|
||||||
|
}
|
||||||
void remove(id_t id) {
|
LPVOID mem = ::MapViewOfFile(ii->h_, FILE_MAP_ALL_ACCESS, 0, 0, 0);
|
||||||
if (id == nullptr) {
|
if (mem == NULL) {
|
||||||
ipc::error("fail release: invalid id (null)\n");
|
ipc::error("fail MapViewOfFile[%d]\n", static_cast<int>(::GetLastError()));
|
||||||
return;
|
return nullptr;
|
||||||
}
|
}
|
||||||
release(id);
|
MEMORY_BASIC_INFORMATION mem_info;
|
||||||
}
|
if (::VirtualQuery(mem, &mem_info, sizeof(mem_info)) == 0) {
|
||||||
|
ipc::error("fail VirtualQuery[%d]\n", static_cast<int>(::GetLastError()));
|
||||||
void remove(char const * name) {
|
return nullptr;
|
||||||
if (!is_valid_string(name)) {
|
}
|
||||||
ipc::error("fail remove: name is empty\n");
|
std::size_t actual_size = static_cast<std::size_t>(mem_info.RegionSize);
|
||||||
return;
|
if (ii->size_ == 0) {
|
||||||
}
|
// Opening existing shared memory
|
||||||
// Do Nothing.
|
ii->size_ = actual_size - sizeof(info_t);
|
||||||
}
|
}
|
||||||
|
// else: Keep user-requested size (already set in acquire)
|
||||||
} // namespace shm
|
ii->mem_ = mem;
|
||||||
} // namespace ipc
|
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,6 +1,10 @@
|
|||||||
#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>
|
||||||
|
|||||||
@ -5,11 +5,11 @@
|
|||||||
namespace ipc {
|
namespace ipc {
|
||||||
namespace mem {
|
namespace mem {
|
||||||
|
|
||||||
void* pool_alloc::alloc(std::size_t size) {
|
void* pool_alloc::alloc(std::size_t size) noexcept {
|
||||||
return async_pool_alloc::alloc(size);
|
return async_pool_alloc::alloc(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pool_alloc::free(void* p, std::size_t size) {
|
void pool_alloc::free(void* p, std::size_t size) noexcept {
|
||||||
async_pool_alloc::free(p, size);
|
async_pool_alloc::free(p, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -55,8 +55,17 @@ 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;
|
||||||
|
|
||||||
bool connected() const noexcept {
|
void clear() noexcept {
|
||||||
return connected_ != 0;
|
elems_h_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clear_storage(char const *name) noexcept {
|
||||||
|
shm::handle::clear_storage(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Elems>
|
||||||
|
bool connected(Elems* elems) const noexcept {
|
||||||
|
return elems->connected(connected_);
|
||||||
}
|
}
|
||||||
|
|
||||||
circ::cc_t connected_id() const noexcept {
|
circ::cc_t connected_id() const noexcept {
|
||||||
@ -69,16 +78,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()) return {connected(), false, 0};
|
if (connected(elems)) return {connected(elems), false, 0};
|
||||||
connected_ = elems->connect_receiver();
|
connected_ = elems->connect_receiver();
|
||||||
return {connected(), true, elems->cursor()};
|
return {connected(elems), 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()) return false;
|
if (!connected(elems)) return false;
|
||||||
elems->disconnect_receiver(std::exchange(connected_, 0));
|
elems->disconnect_receiver(std::exchange(connected_, 0));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -123,6 +132,11 @@ public:
|
|||||||
return elems_ != nullptr;
|
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_; }
|
||||||
|
|
||||||
@ -137,6 +151,10 @@ 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)) {
|
||||||
|
|||||||
@ -78,8 +78,12 @@ bool handle::acquire(char const * name, std::size_t size, unsigned mode) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
release();
|
release();
|
||||||
|
const auto id = shm::acquire(name, size, mode);
|
||||||
|
if (!id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
impl(p_)->id_ = id;
|
||||||
impl(p_)->n_ = name;
|
impl(p_)->n_ = name;
|
||||||
impl(p_)->id_ = shm::acquire(name, size, mode);
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
@ -89,6 +93,18 @@ std::int32_t handle::release() {
|
|||||||
return shm::release(detach());
|
return shm::release(detach());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handle::clear() noexcept {
|
||||||
|
if (impl(p_)->id_ == nullptr) return;
|
||||||
|
shm::remove(detach());
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle::clear_storage(char const * name) noexcept {
|
||||||
|
if (name == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
shm::remove(name);
|
||||||
|
}
|
||||||
|
|
||||||
void* handle::get() const {
|
void* handle::get() const {
|
||||||
return impl(p_)->m_;
|
return impl(p_)->m_;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
#include "libipc/platform/win/condition.h"
|
#include "libipc/platform/win/condition.h"
|
||||||
#elif defined(IPC_OS_LINUX_)
|
#elif defined(IPC_OS_LINUX_)
|
||||||
#include "libipc/platform/linux/condition.h"
|
#include "libipc/platform/linux/condition.h"
|
||||||
#elif defined(IPC_OS_QNX_)
|
#elif defined(IPC_OS_QNX_) || defined(IPC_OS_FREEBSD_)
|
||||||
#include "libipc/platform/posix/condition.h"
|
#include "libipc/platform/posix/condition.h"
|
||||||
#else/*IPC_OS*/
|
#else/*IPC_OS*/
|
||||||
# error "Unsupported platform."
|
# error "Unsupported platform."
|
||||||
@ -61,6 +61,14 @@ void condition::close() noexcept {
|
|||||||
impl(p_)->cond_.close();
|
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 {
|
bool condition::wait(ipc::sync::mutex &mtx, std::uint64_t tm) noexcept {
|
||||||
return impl(p_)->cond_.wait(mtx, tm);
|
return impl(p_)->cond_.wait(mtx, tm);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
#include "libipc/platform/win/mutex.h"
|
#include "libipc/platform/win/mutex.h"
|
||||||
#elif defined(IPC_OS_LINUX_)
|
#elif defined(IPC_OS_LINUX_)
|
||||||
#include "libipc/platform/linux/mutex.h"
|
#include "libipc/platform/linux/mutex.h"
|
||||||
#elif defined(IPC_OS_QNX_)
|
#elif defined(IPC_OS_QNX_) || defined(IPC_OS_FREEBSD_)
|
||||||
#include "libipc/platform/posix/mutex.h"
|
#include "libipc/platform/posix/mutex.h"
|
||||||
#else/*IPC_OS*/
|
#else/*IPC_OS*/
|
||||||
# error "Unsupported platform."
|
# error "Unsupported platform."
|
||||||
@ -61,6 +61,14 @@ void mutex::close() noexcept {
|
|||||||
impl(p_)->lock_.close();
|
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 {
|
bool mutex::lock(std::uint64_t tm) noexcept {
|
||||||
return impl(p_)->lock_.lock(tm);
|
return impl(p_)->lock_.lock(tm);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
#include "libipc/platform/detail.h"
|
#include "libipc/platform/detail.h"
|
||||||
#if defined(IPC_OS_WINDOWS_)
|
#if defined(IPC_OS_WINDOWS_)
|
||||||
#include "libipc/platform/win/semaphore.h"
|
#include "libipc/platform/win/semaphore.h"
|
||||||
#elif defined(IPC_OS_LINUX_) || defined(IPC_OS_QNX_)
|
#elif defined(IPC_OS_LINUX_) || defined(IPC_OS_QNX_) || defined(IPC_OS_FREEBSD_)
|
||||||
#include "libipc/platform/posix/semaphore_impl.h"
|
#include "libipc/platform/posix/semaphore_impl.h"
|
||||||
#else/*IPC_OS*/
|
#else/*IPC_OS*/
|
||||||
# error "Unsupported platform."
|
# error "Unsupported platform."
|
||||||
@ -59,6 +59,14 @@ void semaphore::close() noexcept {
|
|||||||
impl(p_)->sem_.close();
|
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 {
|
bool semaphore::wait(std::uint64_t tm) noexcept {
|
||||||
return impl(p_)->sem_.wait(tm);
|
return impl(p_)->sem_.wait(tm);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
#include "libipc/platform/win/mutex.h"
|
#include "libipc/platform/win/mutex.h"
|
||||||
#elif defined(IPC_OS_LINUX_)
|
#elif defined(IPC_OS_LINUX_)
|
||||||
#include "libipc/platform/linux/mutex.h"
|
#include "libipc/platform/linux/mutex.h"
|
||||||
#elif defined(IPC_OS_QNX_)
|
#elif defined(IPC_OS_QNX_) || defined(IPC_OS_FREEBSD_)
|
||||||
#include "libipc/platform/posix/mutex.h"
|
#include "libipc/platform/posix/mutex.h"
|
||||||
#else/*IPC_OS*/
|
#else/*IPC_OS*/
|
||||||
# error "Unsupported platform."
|
# error "Unsupported platform."
|
||||||
|
|||||||
@ -51,6 +51,16 @@ public:
|
|||||||
lock_.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>
|
template <typename F>
|
||||||
bool wait_if(F &&pred, std::uint64_t tm = ipc::invalid_value) noexcept {
|
bool wait_if(F &&pred, std::uint64_t tm = ipc::invalid_value) noexcept {
|
||||||
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> guard {lock_};
|
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> guard {lock_};
|
||||||
|
|||||||
10
test/CMakeLists.txt
Executable file → Normal file
10
test/CMakeLists.txt
Executable file → Normal file
@ -15,11 +15,15 @@ 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
|
file(GLOB SRC_FILES
|
||||||
${LIBIPC_PROJECT_DIR}/test/*.cpp
|
${LIBIPC_PROJECT_DIR}/test/test_*.cpp
|
||||||
# ${LIBIPC_PROJECT_DIR}/test/profiler/*.cpp
|
|
||||||
)
|
)
|
||||||
file(GLOB HEAD_FILES ${LIBIPC_PROJECT_DIR}/test/*.h)
|
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})
|
||||||
|
|
||||||
|
|||||||
28
test/archive/CMakeLists.txt.old
Executable file
28
test/archive/CMakeLists.txt.old
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
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,86 +1,110 @@
|
|||||||
#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"
|
||||||
|
|
||||||
namespace ipc_ut {
|
#include "libipc/platform/detail.h"
|
||||||
|
#ifdef IPC_OS_LINUX_
|
||||||
template <typename Dur>
|
#include <fcntl.h> // ::open
|
||||||
struct unit;
|
#endif
|
||||||
|
|
||||||
template <> struct unit<std::chrono::nanoseconds> {
|
namespace ipc_ut {
|
||||||
constexpr static char const * str() noexcept {
|
|
||||||
return "ns";
|
template <typename Dur>
|
||||||
}
|
struct unit;
|
||||||
};
|
|
||||||
|
template <> struct unit<std::chrono::nanoseconds> {
|
||||||
template <> struct unit<std::chrono::microseconds> {
|
constexpr static char const * str() noexcept {
|
||||||
constexpr static char const * str() noexcept {
|
return "ns";
|
||||||
return "us";
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
template <> struct unit<std::chrono::microseconds> {
|
||||||
template <> struct unit<std::chrono::milliseconds> {
|
constexpr static char const * str() noexcept {
|
||||||
constexpr static char const * str() noexcept {
|
return "us";
|
||||||
return "ms";
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
template <> struct unit<std::chrono::milliseconds> {
|
||||||
template <> struct unit<std::chrono::seconds> {
|
constexpr static char const * str() noexcept {
|
||||||
constexpr static char const * str() noexcept {
|
return "ms";
|
||||||
return "sec";
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
template <> struct unit<std::chrono::seconds> {
|
||||||
struct test_stopwatch {
|
constexpr static char const * str() noexcept {
|
||||||
capo::stopwatch<> sw_;
|
return "sec";
|
||||||
std::atomic_flag started_ = ATOMIC_FLAG_INIT;
|
}
|
||||||
|
};
|
||||||
void start() {
|
|
||||||
if (!started_.test_and_set()) {
|
struct test_stopwatch {
|
||||||
sw_.start();
|
capo::stopwatch<> sw_;
|
||||||
}
|
std::atomic_flag started_ = ATOMIC_FLAG_INIT;
|
||||||
}
|
|
||||||
|
void start() {
|
||||||
template <typename ToDur = std::chrono::nanoseconds>
|
if (!started_.test_and_set()) {
|
||||||
void print_elapsed(int N, int Loops, char const * message = "") {
|
sw_.start();
|
||||||
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 = "") {
|
||||||
template <int Factor, typename ToDur = std::chrono::nanoseconds>
|
auto ts = sw_.elapsed<ToDur>();
|
||||||
void print_elapsed(int N, int M, int Loops, char const * message = "") {
|
std::cout << "[" << N << ", \t" << Loops << "] " << message << "\t"
|
||||||
auto ts = sw_.elapsed<ToDur>();
|
<< (double(ts) / double(Loops)) << " " << unit<ToDur>::str() << std::endl;
|
||||||
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 = "") {
|
||||||
template <typename ToDur = std::chrono::nanoseconds>
|
auto ts = sw_.elapsed<ToDur>();
|
||||||
void print_elapsed(int N, int M, int Loops, char const * message = "") {
|
std::cout << "[" << N << "-" << M << ", \t" << Loops << "] " << message << "\t"
|
||||||
print_elapsed<0, ToDur>(N, M, Loops, message);
|
<< (double(ts) / double(Factor ? (Loops * Factor) : (Loops * N))) << " " << unit<ToDur>::str() << std::endl;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
template <typename ToDur = std::chrono::nanoseconds>
|
||||||
inline static thread_pool & sender() {
|
void print_elapsed(int N, int M, int Loops, char const * message = "") {
|
||||||
static thread_pool pool;
|
print_elapsed<0, ToDur>(N, M, Loops, message);
|
||||||
return pool;
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
inline static thread_pool & reader() {
|
inline static thread_pool & sender() {
|
||||||
static thread_pool pool;
|
static thread_pool pool;
|
||||||
return pool;
|
return pool;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ipc_ut
|
inline static thread_pool & reader() {
|
||||||
|
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
|
||||||
@ -151,6 +151,50 @@ void test_sr(char const * name, int s_cnt, int r_cnt) {
|
|||||||
|
|
||||||
} // internal-linkage
|
} // internal-linkage
|
||||||
|
|
||||||
|
TEST(IPC, clear) {
|
||||||
|
{
|
||||||
|
chan<relat::single, relat::single, trans::unicast> c{"ssu"};
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__AC_CONN__ssu", true));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__CC_CONN__ssu_WAITER_COND_", true));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__CC_CONN__ssu_WAITER_LOCK_", true));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__RD_CONN__ssu_WAITER_COND_", true));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__RD_CONN__ssu_WAITER_LOCK_", true));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__WT_CONN__ssu_WAITER_COND_", true));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__WT_CONN__ssu_WAITER_LOCK_", true));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__QU_CONN__ssu__64__16", true));
|
||||||
|
c.clear();
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__AC_CONN__ssu", false));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__CC_CONN__ssu_WAITER_COND_", false));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__CC_CONN__ssu_WAITER_LOCK_", false));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__RD_CONN__ssu_WAITER_COND_", false));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__RD_CONN__ssu_WAITER_LOCK_", false));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__WT_CONN__ssu_WAITER_COND_", false));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__WT_CONN__ssu_WAITER_LOCK_", false));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__QU_CONN__ssu__64__16", false));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
chan<relat::single, relat::single, trans::unicast> c{"ssu"};
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__AC_CONN__ssu", true));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__CC_CONN__ssu_WAITER_COND_", true));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__CC_CONN__ssu_WAITER_LOCK_", true));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__RD_CONN__ssu_WAITER_COND_", true));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__RD_CONN__ssu_WAITER_LOCK_", true));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__WT_CONN__ssu_WAITER_COND_", true));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__WT_CONN__ssu_WAITER_LOCK_", true));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__QU_CONN__ssu__64__16", true));
|
||||||
|
chan<relat::single, relat::single, trans::unicast>::clear_storage("ssu");
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__AC_CONN__ssu", false));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__CC_CONN__ssu_WAITER_COND_", false));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__CC_CONN__ssu_WAITER_LOCK_", false));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__RD_CONN__ssu_WAITER_COND_", false));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__RD_CONN__ssu_WAITER_LOCK_", false));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__WT_CONN__ssu_WAITER_COND_", false));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__WT_CONN__ssu_WAITER_LOCK_", false));
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("__IPC_SHM__QU_CONN__ssu__64__16", false));
|
||||||
|
c.release(); // Call this interface to prevent destruction-time exceptions.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST(IPC, basic_ssu) {
|
TEST(IPC, basic_ssu) {
|
||||||
test_basic<relat::single, relat::single, trans::unicast >("ssu");
|
test_basic<relat::single, relat::single, trans::unicast >("ssu");
|
||||||
}
|
}
|
||||||
@ -1,304 +1,311 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <new>
|
#include <new>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <climits> // CHAR_BIT
|
#include <climits> // CHAR_BIT
|
||||||
|
|
||||||
#include "libipc/prod_cons.h"
|
#include "libipc/prod_cons.h"
|
||||||
#include "libipc/policy.h"
|
#include "libipc/policy.h"
|
||||||
#include "libipc/circ/elem_array.h"
|
#include "libipc/circ/elem_array.h"
|
||||||
#include "libipc/queue.h"
|
#include "libipc/queue.h"
|
||||||
|
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
struct msg_t {
|
struct msg_t {
|
||||||
int pid_;
|
int pid_;
|
||||||
int dat_;
|
int dat_;
|
||||||
|
|
||||||
msg_t() = default;
|
msg_t() = default;
|
||||||
msg_t(int p, int d) : pid_(p), dat_(d) {}
|
msg_t(int p, int d) : pid_(p), dat_(d) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <ipc::relat Rp, ipc::relat Rc, ipc::trans Ts>
|
template <ipc::relat Rp, ipc::relat Rc, ipc::trans Ts>
|
||||||
using queue_t = ipc::queue<msg_t, ipc::policy::choose<ipc::circ::elem_array, ipc::wr<Rp, Rc, Ts>>>;
|
using queue_t = ipc::queue<msg_t, ipc::policy::choose<ipc::circ::elem_array, ipc::wr<Rp, Rc, Ts>>>;
|
||||||
|
|
||||||
template <ipc::relat Rp, ipc::relat Rc, ipc::trans Ts>
|
template <ipc::relat Rp, ipc::relat Rc, ipc::trans Ts>
|
||||||
struct elems_t : public queue_t<Rp, Rc, Ts>::elems_t {};
|
struct elems_t : public queue_t<Rp, Rc, Ts>::elems_t {};
|
||||||
|
|
||||||
bool operator==(msg_t const & m1, msg_t const & m2) noexcept {
|
bool operator==(msg_t const & m1, msg_t const & m2) noexcept {
|
||||||
return (m1.pid_ == m2.pid_) && (m1.dat_ == m2.dat_);
|
return (m1.pid_ == m2.pid_) && (m1.dat_ == m2.dat_);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator!=(msg_t const & m1, msg_t const & m2) noexcept {
|
bool operator!=(msg_t const & m1, msg_t const & m2) noexcept {
|
||||||
return !(m1 == m2);
|
return !(m1 == m2);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr int LoopCount = 1000000;
|
constexpr int LoopCount = 1000000;
|
||||||
constexpr int PushRetry = 1000000;
|
constexpr int PushRetry = 1000000;
|
||||||
constexpr int ThreadMax = 8;
|
constexpr int ThreadMax = 8;
|
||||||
|
|
||||||
template <typename Que>
|
template <typename Que>
|
||||||
void push(Que & que, int p, int d) {
|
void push(Que & que, int p, int d) {
|
||||||
for (int n = 0; !que.push([](void*) { return true; }, p, d); ++n) {
|
for (int n = 0; !que.push([](void*) { return true; }, p, d); ++n) {
|
||||||
ASSERT_NE(n, PushRetry);
|
ASSERT_NE(n, PushRetry);
|
||||||
std::this_thread::yield();
|
std::this_thread::yield();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Que>
|
template <typename Que>
|
||||||
msg_t pop(Que & que) {
|
msg_t pop(Que & que) {
|
||||||
msg_t msg;
|
msg_t msg;
|
||||||
while (!que.pop(msg)) {
|
while (!que.pop(msg)) {
|
||||||
std::this_thread::yield();
|
std::this_thread::yield();
|
||||||
}
|
}
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <ipc::trans Ts>
|
template <ipc::trans Ts>
|
||||||
struct quitter;
|
struct quitter;
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
struct quitter<ipc::trans::unicast> {
|
struct quitter<ipc::trans::unicast> {
|
||||||
template <typename Que>
|
template <typename Que>
|
||||||
static void emit(Que && que, int r_cnt) {
|
static void emit(Que && que, int r_cnt) {
|
||||||
for (int k = 0; k < r_cnt; ++k) {
|
for (int k = 0; k < r_cnt; ++k) {
|
||||||
push(que, -1, -1);
|
push(que, -1, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
struct quitter<ipc::trans::broadcast> {
|
struct quitter<ipc::trans::broadcast> {
|
||||||
template <typename Que>
|
template <typename Que>
|
||||||
static void emit(Que && que, int /*r_cnt*/) {
|
static void emit(Que && que, int /*r_cnt*/) {
|
||||||
push(que, -1, -1);
|
push(que, -1, -1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <ipc::relat Rp, ipc::relat Rc, ipc::trans Ts>
|
template <ipc::relat Rp, ipc::relat Rc, ipc::trans Ts>
|
||||||
void test_sr(elems_t<Rp, Rc, Ts> && elems, int s_cnt, int r_cnt, char const * message) {
|
void test_sr(elems_t<Rp, Rc, Ts> && elems, int s_cnt, int r_cnt, char const * message) {
|
||||||
ipc_ut::sender().start(static_cast<std::size_t>(s_cnt));
|
ipc_ut::sender().start(static_cast<std::size_t>(s_cnt));
|
||||||
ipc_ut::reader().start(static_cast<std::size_t>(r_cnt));
|
ipc_ut::reader().start(static_cast<std::size_t>(r_cnt));
|
||||||
ipc_ut::test_stopwatch sw;
|
ipc_ut::test_stopwatch sw;
|
||||||
|
|
||||||
for (int k = 0; k < s_cnt; ++k) {
|
for (int k = 0; k < s_cnt; ++k) {
|
||||||
ipc_ut::sender() << [&elems, &sw, r_cnt, k] {
|
ipc_ut::sender() << [&elems, &sw, r_cnt, k] {
|
||||||
queue_t<Rp, Rc, Ts> que { &elems };
|
queue_t<Rp, Rc, Ts> que { &elems };
|
||||||
while (que.conn_count() != static_cast<std::size_t>(r_cnt)) {
|
while (que.conn_count() != static_cast<std::size_t>(r_cnt)) {
|
||||||
std::this_thread::yield();
|
std::this_thread::yield();
|
||||||
}
|
}
|
||||||
sw.start();
|
sw.start();
|
||||||
for (int i = 0; i < LoopCount; ++i) {
|
for (int i = 0; i < LoopCount; ++i) {
|
||||||
push(que, k, i);
|
push(que, k, i);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
for (int k = 0; k < r_cnt; ++k) {
|
for (int k = 0; k < r_cnt; ++k) {
|
||||||
ipc_ut::reader() << [&elems, k] {
|
ipc_ut::reader() << [&elems, k] {
|
||||||
queue_t<Rp, Rc, Ts> que { &elems };
|
queue_t<Rp, Rc, Ts> que { &elems };
|
||||||
ASSERT_TRUE(que.connect());
|
ASSERT_TRUE(que.connect());
|
||||||
while (pop(que).pid_ >= 0) ;
|
while (pop(que).pid_ >= 0) ;
|
||||||
ASSERT_TRUE(que.disconnect());
|
ASSERT_TRUE(que.disconnect());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
ipc_ut::sender().wait_for_done();
|
ipc_ut::sender().wait_for_done();
|
||||||
quitter<Ts>::emit(queue_t<Rp, Rc, Ts> { &elems }, r_cnt);
|
quitter<Ts>::emit(queue_t<Rp, Rc, Ts> { &elems }, r_cnt);
|
||||||
ipc_ut::reader().wait_for_done();
|
ipc_ut::reader().wait_for_done();
|
||||||
sw.print_elapsed(s_cnt, r_cnt, LoopCount, message);
|
sw.print_elapsed(s_cnt, r_cnt, LoopCount, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // internal-linkage
|
} // internal-linkage
|
||||||
|
|
||||||
TEST(Queue, check_size) {
|
TEST(Queue, check_size) {
|
||||||
using el_t = elems_t<ipc::relat::single, ipc::relat::multi, ipc::trans::broadcast>;
|
using el_t = elems_t<ipc::relat::single, ipc::relat::multi, ipc::trans::broadcast>;
|
||||||
|
|
||||||
std::cout << "cq_t::head_size = " << el_t::head_size << std::endl;
|
std::cout << "cq_t::head_size = " << el_t::head_size << std::endl;
|
||||||
std::cout << "cq_t::data_size = " << el_t::data_size << std::endl;
|
std::cout << "cq_t::data_size = " << el_t::data_size << std::endl;
|
||||||
std::cout << "cq_t::elem_size = " << el_t::elem_size << std::endl;
|
std::cout << "cq_t::elem_size = " << el_t::elem_size << std::endl;
|
||||||
std::cout << "cq_t::block_size = " << el_t::block_size << std::endl;
|
std::cout << "cq_t::block_size = " << el_t::block_size << std::endl;
|
||||||
|
|
||||||
EXPECT_EQ(static_cast<std::size_t>(el_t::data_size), sizeof(msg_t));
|
EXPECT_EQ(static_cast<std::size_t>(el_t::data_size), sizeof(msg_t));
|
||||||
|
|
||||||
std::cout << "sizeof(elems_t<s, m, b>) = " << sizeof(el_t) << std::endl;
|
std::cout << "sizeof(elems_t<s, m, b>) = " << sizeof(el_t) << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Queue, el_connection) {
|
TEST(Queue, el_connection) {
|
||||||
{
|
{
|
||||||
elems_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> el;
|
elems_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> el;
|
||||||
EXPECT_TRUE(el.connect_sender());
|
EXPECT_TRUE(el.connect_sender());
|
||||||
for (std::size_t i = 0; i < 10000; ++i) {
|
for (std::size_t i = 0; i < 10000; ++i) {
|
||||||
ASSERT_FALSE(el.connect_sender());
|
ASSERT_FALSE(el.connect_sender());
|
||||||
}
|
}
|
||||||
el.disconnect_sender();
|
el.disconnect_sender();
|
||||||
EXPECT_TRUE(el.connect_sender());
|
EXPECT_TRUE(el.connect_sender());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
elems_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::unicast> el;
|
elems_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::unicast> el;
|
||||||
for (std::size_t i = 0; i < 10000; ++i) {
|
for (std::size_t i = 0; i < 10000; ++i) {
|
||||||
ASSERT_TRUE(el.connect_sender());
|
ASSERT_TRUE(el.connect_sender());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
elems_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> el;
|
elems_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> el;
|
||||||
auto cc = el.connect_receiver();
|
auto cc = el.connect_receiver();
|
||||||
EXPECT_NE(cc, 0);
|
EXPECT_NE(cc, 0);
|
||||||
for (std::size_t i = 0; i < 10000; ++i) {
|
for (std::size_t i = 0; i < 10000; ++i) {
|
||||||
ASSERT_EQ(el.connect_receiver(), 0);
|
ASSERT_EQ(el.connect_receiver(), 0);
|
||||||
}
|
}
|
||||||
EXPECT_EQ(el.disconnect_receiver(cc), 0);
|
EXPECT_EQ(el.disconnect_receiver(cc), 0);
|
||||||
EXPECT_EQ(el.connect_receiver(), cc);
|
EXPECT_EQ(el.connect_receiver(), cc);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
elems_t<ipc::relat::single, ipc::relat::multi, ipc::trans::broadcast> el;
|
elems_t<ipc::relat::single, ipc::relat::multi, ipc::trans::broadcast> el;
|
||||||
for (std::size_t i = 0; i < (sizeof(ipc::circ::cc_t) * CHAR_BIT); ++i) {
|
for (std::size_t i = 0; i < (sizeof(ipc::circ::cc_t) * CHAR_BIT); ++i) {
|
||||||
ASSERT_NE(el.connect_receiver(), 0);
|
ASSERT_NE(el.connect_receiver(), 0);
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < 10000; ++i) {
|
for (std::size_t i = 0; i < 10000; ++i) {
|
||||||
ASSERT_EQ(el.connect_receiver(), 0);
|
ASSERT_EQ(el.connect_receiver(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Queue, connection) {
|
TEST(Queue, connection) {
|
||||||
{
|
{
|
||||||
elems_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> el;
|
elems_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> el;
|
||||||
queue_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> que{&el};
|
queue_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> que{&el};
|
||||||
// sending
|
// sending
|
||||||
for (std::size_t i = 0; i < 10000; ++i) {
|
for (std::size_t i = 0; i < 10000; ++i) {
|
||||||
ASSERT_TRUE(que.ready_sending());
|
ASSERT_TRUE(que.ready_sending());
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < 10000; ++i) {
|
for (std::size_t i = 0; i < 10000; ++i) {
|
||||||
queue_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> que{&el};
|
queue_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> que{&el};
|
||||||
ASSERT_FALSE(que.ready_sending());
|
ASSERT_FALSE(que.ready_sending());
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < 10000; ++i) {
|
for (std::size_t i = 0; i < 10000; ++i) {
|
||||||
que.shut_sending();
|
que.shut_sending();
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
queue_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> que{&el};
|
queue_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> que{&el};
|
||||||
EXPECT_TRUE(que.ready_sending());
|
EXPECT_TRUE(que.ready_sending());
|
||||||
}
|
}
|
||||||
// receiving
|
// receiving
|
||||||
for (std::size_t i = 0; i < 10000; ++i) {
|
for (std::size_t i = 0; i < 10000; ++i) {
|
||||||
ASSERT_TRUE(que.connect());
|
ASSERT_TRUE(que.connect());
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < 10000; ++i) {
|
for (std::size_t i = 0; i < 10000; ++i) {
|
||||||
queue_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> que{&el};
|
queue_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> que{&el};
|
||||||
ASSERT_FALSE(que.connect());
|
ASSERT_FALSE(que.connect());
|
||||||
}
|
}
|
||||||
EXPECT_TRUE(que.disconnect());
|
EXPECT_TRUE(que.disconnect());
|
||||||
for (std::size_t i = 0; i < 10000; ++i) {
|
for (std::size_t i = 0; i < 10000; ++i) {
|
||||||
ASSERT_FALSE(que.disconnect());
|
ASSERT_FALSE(que.disconnect());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
queue_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> que{&el};
|
queue_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> que{&el};
|
||||||
EXPECT_TRUE(que.connect());
|
EXPECT_TRUE(que.connect());
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < 10000; ++i) {
|
for (std::size_t i = 0; i < 10000; ++i) {
|
||||||
queue_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> que{&el};
|
queue_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> que{&el};
|
||||||
ASSERT_FALSE(que.connect());
|
ASSERT_FALSE(que.connect());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
elems_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> el;
|
elems_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> el;
|
||||||
queue_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> que{&el};
|
queue_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> que{&el};
|
||||||
// sending
|
// sending
|
||||||
for (std::size_t i = 0; i < 10000; ++i) {
|
for (std::size_t i = 0; i < 10000; ++i) {
|
||||||
ASSERT_TRUE(que.ready_sending());
|
ASSERT_TRUE(que.ready_sending());
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < 10000; ++i) {
|
for (std::size_t i = 0; i < 10000; ++i) {
|
||||||
queue_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> que{&el};
|
queue_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> que{&el};
|
||||||
ASSERT_TRUE(que.ready_sending());
|
ASSERT_TRUE(que.ready_sending());
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < 10000; ++i) {
|
for (std::size_t i = 0; i < 10000; ++i) {
|
||||||
que.shut_sending();
|
que.shut_sending();
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < 10000; ++i) {
|
for (std::size_t i = 0; i < 10000; ++i) {
|
||||||
queue_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> que{&el};
|
queue_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> que{&el};
|
||||||
ASSERT_TRUE(que.ready_sending());
|
ASSERT_TRUE(que.ready_sending());
|
||||||
}
|
}
|
||||||
// receiving
|
// receiving
|
||||||
for (std::size_t i = 0; i < 10000; ++i) {
|
for (std::size_t i = 0; i < 10000; ++i) {
|
||||||
ASSERT_TRUE(que.connect());
|
ASSERT_TRUE(que.connect());
|
||||||
}
|
}
|
||||||
for (std::size_t i = 1; i < (sizeof(ipc::circ::cc_t) * CHAR_BIT); ++i) {
|
for (std::size_t i = 1; i < (sizeof(ipc::circ::cc_t) * CHAR_BIT); ++i) {
|
||||||
queue_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> que{&el};
|
queue_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> que{&el};
|
||||||
ASSERT_TRUE(que.connect());
|
ASSERT_TRUE(que.connect());
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < 10000; ++i) {
|
for (std::size_t i = 0; i < 10000; ++i) {
|
||||||
queue_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> que{&el};
|
queue_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> que{&el};
|
||||||
ASSERT_FALSE(que.connect());
|
ASSERT_FALSE(que.connect());
|
||||||
}
|
}
|
||||||
ASSERT_TRUE(que.disconnect());
|
ASSERT_TRUE(que.disconnect());
|
||||||
for (std::size_t i = 0; i < 10000; ++i) {
|
for (std::size_t i = 0; i < 10000; ++i) {
|
||||||
ASSERT_FALSE(que.disconnect());
|
ASSERT_FALSE(que.disconnect());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
queue_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> que{&el};
|
queue_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> que{&el};
|
||||||
ASSERT_TRUE(que.connect());
|
ASSERT_TRUE(que.connect());
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < 10000; ++i) {
|
for (std::size_t i = 0; i < 10000; ++i) {
|
||||||
queue_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> que{&el};
|
queue_t<ipc::relat::multi, ipc::relat::multi, ipc::trans::broadcast> que{&el};
|
||||||
ASSERT_FALSE(que.connect());
|
ASSERT_FALSE(que.connect());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Queue, prod_cons_1v1_unicast) {
|
TEST(Queue, prod_cons_1v1_unicast) {
|
||||||
test_sr(elems_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast>{}, 1, 1, "ssu");
|
test_sr(elems_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast>{}, 1, 1, "ssu");
|
||||||
test_sr(elems_t<ipc::relat::single, ipc::relat::multi , ipc::trans::unicast>{}, 1, 1, "smu");
|
test_sr(elems_t<ipc::relat::single, ipc::relat::multi , ipc::trans::unicast>{}, 1, 1, "smu");
|
||||||
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::unicast>{}, 1, 1, "mmu");
|
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::unicast>{}, 1, 1, "mmu");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Queue, prod_cons_1v1_broadcast) {
|
TEST(Queue, prod_cons_1v1_broadcast) {
|
||||||
test_sr(elems_t<ipc::relat::single, ipc::relat::multi , ipc::trans::broadcast>{}, 1, 1, "smb");
|
test_sr(elems_t<ipc::relat::single, ipc::relat::multi , ipc::trans::broadcast>{}, 1, 1, "smb");
|
||||||
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::broadcast>{}, 1, 1, "mmb");
|
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::broadcast>{}, 1, 1, "mmb");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Queue, prod_cons_1vN_unicast) {
|
TEST(Queue, prod_cons_1vN_unicast) {
|
||||||
for (int i = 1; i <= ThreadMax; ++i) {
|
for (int i = 1; i <= ThreadMax; ++i) {
|
||||||
test_sr(elems_t<ipc::relat::single, ipc::relat::multi , ipc::trans::unicast>{}, 1, i, "smu");
|
test_sr(elems_t<ipc::relat::single, ipc::relat::multi , ipc::trans::unicast>{}, 1, i, "smu");
|
||||||
}
|
}
|
||||||
for (int i = 1; i <= ThreadMax; ++i) {
|
for (int i = 1; i <= ThreadMax; ++i) {
|
||||||
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::unicast>{}, 1, i, "mmu");
|
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::unicast>{}, 1, i, "mmu");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Queue, prod_cons_1vN_broadcast) {
|
TEST(Queue, prod_cons_1vN_broadcast) {
|
||||||
for (int i = 1; i <= ThreadMax; ++i) {
|
for (int i = 1; i <= ThreadMax; ++i) {
|
||||||
test_sr(elems_t<ipc::relat::single, ipc::relat::multi , ipc::trans::broadcast>{}, 1, i, "smb");
|
test_sr(elems_t<ipc::relat::single, ipc::relat::multi , ipc::trans::broadcast>{}, 1, i, "smb");
|
||||||
}
|
}
|
||||||
for (int i = 1; i <= ThreadMax; ++i) {
|
for (int i = 1; i <= ThreadMax; ++i) {
|
||||||
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::broadcast>{}, 1, i, "mmb");
|
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::broadcast>{}, 1, i, "mmb");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Queue, prod_cons_NvN_unicast) {
|
TEST(Queue, prod_cons_NvN_unicast) {
|
||||||
for (int i = 1; i <= ThreadMax; ++i) {
|
for (int i = 1; i <= ThreadMax; ++i) {
|
||||||
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::unicast>{}, 1, i, "mmu");
|
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::unicast>{}, 1, i, "mmu");
|
||||||
}
|
}
|
||||||
for (int i = 1; i <= ThreadMax; ++i) {
|
for (int i = 1; i <= ThreadMax; ++i) {
|
||||||
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::unicast>{}, i, 1, "mmu");
|
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::unicast>{}, i, 1, "mmu");
|
||||||
}
|
}
|
||||||
for (int i = 1; i <= ThreadMax; ++i) {
|
for (int i = 1; i <= ThreadMax; ++i) {
|
||||||
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::unicast>{}, i, i, "mmu");
|
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::unicast>{}, i, i, "mmu");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Queue, prod_cons_NvN_broadcast) {
|
TEST(Queue, prod_cons_NvN_broadcast) {
|
||||||
for (int i = 1; i <= ThreadMax; ++i) {
|
for (int i = 1; i <= ThreadMax; ++i) {
|
||||||
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::broadcast>{}, 1, i, "mmb");
|
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::broadcast>{}, 1, i, "mmb");
|
||||||
}
|
}
|
||||||
for (int i = 1; i <= ThreadMax; ++i) {
|
for (int i = 1; i <= ThreadMax; ++i) {
|
||||||
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::broadcast>{}, i, 1, "mmb");
|
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::broadcast>{}, i, 1, "mmb");
|
||||||
}
|
}
|
||||||
for (int i = 1; i <= ThreadMax; ++i) {
|
for (int i = 1; i <= ThreadMax; ++i) {
|
||||||
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::broadcast>{}, i, i, "mmb");
|
test_sr(elems_t<ipc::relat::multi , ipc::relat::multi , ipc::trans::broadcast>{}, i, i, "mmb");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(Queue, clear) {
|
||||||
|
queue_t<ipc::relat::single, ipc::relat::single, ipc::trans::unicast> que{"test-queue-clear"};
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("test-queue-clear", true));
|
||||||
|
que.clear();
|
||||||
|
EXPECT_TRUE(ipc_ut::expect_exist("test-queue-clear", false));
|
||||||
|
}
|
||||||
134
test/archive/test_shm.cpp
Executable file
134
test/archive/test_shm.cpp
Executable file
@ -0,0 +1,134 @@
|
|||||||
|
#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
|
||||||
@ -37,7 +37,11 @@ TEST(PThread, Robust) {
|
|||||||
pthread_mutex_destroy(&mutex);
|
pthread_mutex_destroy(&mutex);
|
||||||
}
|
}
|
||||||
#elif defined(IPC_OS_WINDOWS_)
|
#elif defined(IPC_OS_WINDOWS_)
|
||||||
|
#if defined(__MINGW32__)
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
#include <tchar.h>
|
#include <tchar.h>
|
||||||
|
|
||||||
TEST(PThread, Robust) {
|
TEST(PThread, Robust) {
|
||||||
@ -205,4 +209,4 @@ TEST(Sync, ConditionRobust) {
|
|||||||
ASSERT_TRUE(lock.unlock());
|
ASSERT_TRUE(lock.unlock());
|
||||||
printf("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 6\n");
|
printf("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 6\n");
|
||||||
unlock.join();
|
unlock.join();
|
||||||
}
|
}
|
||||||
@ -4,8 +4,6 @@
|
|||||||
#include "libipc/waiter.h"
|
#include "libipc/waiter.h"
|
||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
TEST(Waiter, broadcast) {
|
TEST(Waiter, broadcast) {
|
||||||
for (int i = 0; i < 10; ++i) {
|
for (int i = 0; i < 10; ++i) {
|
||||||
ipc::detail::waiter waiter;
|
ipc::detail::waiter waiter;
|
||||||
@ -65,4 +63,23 @@ TEST(Waiter, quit_waiting) {
|
|||||||
std::cout << "quit... \n";
|
std::cout << "quit... \n";
|
||||||
}
|
}
|
||||||
|
|
||||||
} // internal-linkage
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
384
test/test_buffer.cpp
Normal file
384
test/test_buffer.cpp
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
550
test/test_condition.cpp
Normal file
550
test/test_condition.cpp
Normal file
@ -0,0 +1,550 @@
|
|||||||
|
/**
|
||||||
|
* @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();
|
||||||
|
}
|
||||||
643
test/test_ipc_channel.cpp
Normal file
643
test/test_ipc_channel.cpp
Normal file
@ -0,0 +1,643 @@
|
|||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
613
test/test_locks.cpp
Normal file
613
test/test_locks.cpp
Normal file
@ -0,0 +1,613 @@
|
|||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
501
test/test_mutex.cpp
Normal file
501
test/test_mutex.cpp
Normal file
@ -0,0 +1,501 @@
|
|||||||
|
/**
|
||||||
|
* @file test_mutex.cpp
|
||||||
|
* @brief Comprehensive unit tests for ipc::sync::mutex class
|
||||||
|
*
|
||||||
|
* This test suite covers:
|
||||||
|
* - Mutex construction (default and named)
|
||||||
|
* - Lock/unlock operations
|
||||||
|
* - Try-lock functionality
|
||||||
|
* - Timed lock with timeout
|
||||||
|
* - Named mutex for inter-process synchronization
|
||||||
|
* - Resource cleanup (clear, clear_storage)
|
||||||
|
* - Native handle access
|
||||||
|
* - Concurrent access scenarios
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
#include <atomic>
|
||||||
|
#include <vector>
|
||||||
|
#include "libipc/mutex.h"
|
||||||
|
#include "libipc/def.h"
|
||||||
|
|
||||||
|
using namespace ipc;
|
||||||
|
using namespace ipc::sync;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Generate unique mutex names for tests
|
||||||
|
std::string generate_unique_mutex_name(const char* prefix) {
|
||||||
|
static int counter = 0;
|
||||||
|
return std::string(prefix) + "_mutex_" + std::to_string(++counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
class MutexTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void TearDown() override {
|
||||||
|
// Allow time for cleanup
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test default constructor
|
||||||
|
TEST_F(MutexTest, DefaultConstructor) {
|
||||||
|
mutex mtx;
|
||||||
|
// Default constructed mutex may or may not be valid depending on implementation
|
||||||
|
// Just ensure it doesn't crash
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test named constructor
|
||||||
|
TEST_F(MutexTest, NamedConstructor) {
|
||||||
|
std::string name = generate_unique_mutex_name("named_ctor");
|
||||||
|
|
||||||
|
mutex mtx(name.c_str());
|
||||||
|
EXPECT_TRUE(mtx.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test native() const method
|
||||||
|
TEST_F(MutexTest, NativeConst) {
|
||||||
|
std::string name = generate_unique_mutex_name("native_const");
|
||||||
|
|
||||||
|
const mutex mtx(name.c_str());
|
||||||
|
const void* native_handle = mtx.native();
|
||||||
|
|
||||||
|
EXPECT_NE(native_handle, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test native() non-const method
|
||||||
|
TEST_F(MutexTest, NativeNonConst) {
|
||||||
|
std::string name = generate_unique_mutex_name("native_nonconst");
|
||||||
|
|
||||||
|
mutex mtx(name.c_str());
|
||||||
|
void* native_handle = mtx.native();
|
||||||
|
|
||||||
|
EXPECT_NE(native_handle, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test valid() method
|
||||||
|
TEST_F(MutexTest, Valid) {
|
||||||
|
mutex mtx1;
|
||||||
|
// May or may not be valid without open
|
||||||
|
|
||||||
|
std::string name = generate_unique_mutex_name("valid");
|
||||||
|
mutex mtx2(name.c_str());
|
||||||
|
EXPECT_TRUE(mtx2.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test open() method
|
||||||
|
TEST_F(MutexTest, Open) {
|
||||||
|
std::string name = generate_unique_mutex_name("open");
|
||||||
|
|
||||||
|
mutex mtx;
|
||||||
|
bool result = mtx.open(name.c_str());
|
||||||
|
|
||||||
|
EXPECT_TRUE(result);
|
||||||
|
EXPECT_TRUE(mtx.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test close() method
|
||||||
|
TEST_F(MutexTest, Close) {
|
||||||
|
std::string name = generate_unique_mutex_name("close");
|
||||||
|
|
||||||
|
mutex mtx(name.c_str());
|
||||||
|
ASSERT_TRUE(mtx.valid());
|
||||||
|
|
||||||
|
mtx.close();
|
||||||
|
EXPECT_FALSE(mtx.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test clear() method
|
||||||
|
TEST_F(MutexTest, Clear) {
|
||||||
|
std::string name = generate_unique_mutex_name("clear");
|
||||||
|
|
||||||
|
mutex mtx(name.c_str());
|
||||||
|
ASSERT_TRUE(mtx.valid());
|
||||||
|
|
||||||
|
mtx.clear();
|
||||||
|
EXPECT_FALSE(mtx.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test clear_storage() static method
|
||||||
|
TEST_F(MutexTest, ClearStorage) {
|
||||||
|
std::string name = generate_unique_mutex_name("clear_storage");
|
||||||
|
|
||||||
|
{
|
||||||
|
mutex mtx(name.c_str());
|
||||||
|
EXPECT_TRUE(mtx.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex::clear_storage(name.c_str());
|
||||||
|
|
||||||
|
// Try to open after clear - should create new or fail gracefully
|
||||||
|
mutex mtx2(name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test basic lock and unlock
|
||||||
|
TEST_F(MutexTest, LockUnlock) {
|
||||||
|
std::string name = generate_unique_mutex_name("lock_unlock");
|
||||||
|
|
||||||
|
mutex mtx(name.c_str());
|
||||||
|
ASSERT_TRUE(mtx.valid());
|
||||||
|
|
||||||
|
bool locked = mtx.lock();
|
||||||
|
EXPECT_TRUE(locked);
|
||||||
|
|
||||||
|
bool unlocked = mtx.unlock();
|
||||||
|
EXPECT_TRUE(unlocked);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test try_lock
|
||||||
|
TEST_F(MutexTest, TryLock) {
|
||||||
|
std::string name = generate_unique_mutex_name("try_lock");
|
||||||
|
|
||||||
|
mutex mtx(name.c_str());
|
||||||
|
ASSERT_TRUE(mtx.valid());
|
||||||
|
|
||||||
|
bool locked = mtx.try_lock();
|
||||||
|
EXPECT_TRUE(locked);
|
||||||
|
|
||||||
|
if (locked) {
|
||||||
|
mtx.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test timed lock with infinite timeout
|
||||||
|
TEST_F(MutexTest, TimedLockInfinite) {
|
||||||
|
std::string name = generate_unique_mutex_name("timed_lock_inf");
|
||||||
|
|
||||||
|
mutex mtx(name.c_str());
|
||||||
|
ASSERT_TRUE(mtx.valid());
|
||||||
|
|
||||||
|
bool locked = mtx.lock(invalid_value);
|
||||||
|
EXPECT_TRUE(locked);
|
||||||
|
|
||||||
|
if (locked) {
|
||||||
|
mtx.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test timed lock with timeout
|
||||||
|
TEST_F(MutexTest, TimedLockTimeout) {
|
||||||
|
std::string name = generate_unique_mutex_name("timed_lock_timeout");
|
||||||
|
|
||||||
|
mutex mtx(name.c_str());
|
||||||
|
ASSERT_TRUE(mtx.valid());
|
||||||
|
|
||||||
|
// Lock with 100ms timeout
|
||||||
|
bool locked = mtx.lock(100);
|
||||||
|
EXPECT_TRUE(locked);
|
||||||
|
|
||||||
|
if (locked) {
|
||||||
|
mtx.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test mutex protects critical section
|
||||||
|
TEST_F(MutexTest, CriticalSection) {
|
||||||
|
std::string name = generate_unique_mutex_name("critical_section");
|
||||||
|
|
||||||
|
mutex mtx(name.c_str());
|
||||||
|
ASSERT_TRUE(mtx.valid());
|
||||||
|
|
||||||
|
int shared_counter = 0;
|
||||||
|
const int iterations = 100;
|
||||||
|
|
||||||
|
auto increment_task = [&]() {
|
||||||
|
for (int i = 0; i < iterations; ++i) {
|
||||||
|
mtx.lock();
|
||||||
|
++shared_counter;
|
||||||
|
mtx.unlock();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::thread t1(increment_task);
|
||||||
|
std::thread t2(increment_task);
|
||||||
|
|
||||||
|
t1.join();
|
||||||
|
t2.join();
|
||||||
|
|
||||||
|
EXPECT_EQ(shared_counter, iterations * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test concurrent try_lock
|
||||||
|
TEST_F(MutexTest, ConcurrentTryLock) {
|
||||||
|
std::string name = generate_unique_mutex_name("concurrent_try");
|
||||||
|
|
||||||
|
mutex mtx(name.c_str());
|
||||||
|
ASSERT_TRUE(mtx.valid());
|
||||||
|
|
||||||
|
std::atomic<int> success_count{0};
|
||||||
|
std::atomic<int> fail_count{0};
|
||||||
|
|
||||||
|
auto try_lock_task = [&]() {
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
if (mtx.try_lock()) {
|
||||||
|
++success_count;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
mtx.unlock();
|
||||||
|
} else {
|
||||||
|
++fail_count;
|
||||||
|
}
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::thread t1(try_lock_task);
|
||||||
|
std::thread t2(try_lock_task);
|
||||||
|
std::thread t3(try_lock_task);
|
||||||
|
|
||||||
|
t1.join();
|
||||||
|
t2.join();
|
||||||
|
t3.join();
|
||||||
|
|
||||||
|
EXPECT_GT(success_count.load(), 0);
|
||||||
|
// Some try_locks should succeed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test lock contention
|
||||||
|
TEST_F(MutexTest, LockContention) {
|
||||||
|
std::string name = generate_unique_mutex_name("contention");
|
||||||
|
|
||||||
|
mutex mtx(name.c_str());
|
||||||
|
ASSERT_TRUE(mtx.valid());
|
||||||
|
|
||||||
|
std::atomic<bool> thread1_in_cs{false};
|
||||||
|
std::atomic<bool> thread2_in_cs{false};
|
||||||
|
std::atomic<bool> violation{false};
|
||||||
|
|
||||||
|
auto contention_task = [&](std::atomic<bool>& my_flag,
|
||||||
|
std::atomic<bool>& other_flag) {
|
||||||
|
for (int i = 0; i < 50; ++i) {
|
||||||
|
mtx.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);
|
||||||
|
mtx.unlock();
|
||||||
|
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::thread t1(contention_task, std::ref(thread1_in_cs), std::ref(thread2_in_cs));
|
||||||
|
std::thread t2(contention_task, std::ref(thread2_in_cs), std::ref(thread1_in_cs));
|
||||||
|
|
||||||
|
t1.join();
|
||||||
|
t2.join();
|
||||||
|
|
||||||
|
// Should never have both threads in critical section
|
||||||
|
EXPECT_FALSE(violation.load());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test multiple lock/unlock cycles
|
||||||
|
TEST_F(MutexTest, MultipleCycles) {
|
||||||
|
std::string name = generate_unique_mutex_name("cycles");
|
||||||
|
|
||||||
|
mutex mtx(name.c_str());
|
||||||
|
ASSERT_TRUE(mtx.valid());
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; ++i) {
|
||||||
|
ASSERT_TRUE(mtx.lock());
|
||||||
|
ASSERT_TRUE(mtx.unlock());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test timed lock timeout scenario
|
||||||
|
TEST_F(MutexTest, TimedLockTimeoutScenario) {
|
||||||
|
std::string name = generate_unique_mutex_name("timeout_scenario");
|
||||||
|
|
||||||
|
mutex mtx(name.c_str());
|
||||||
|
ASSERT_TRUE(mtx.valid());
|
||||||
|
|
||||||
|
// Lock in main thread
|
||||||
|
ASSERT_TRUE(mtx.lock());
|
||||||
|
|
||||||
|
std::atomic<bool> timeout_occurred{false};
|
||||||
|
|
||||||
|
std::thread t([&]() {
|
||||||
|
// Try to lock with short timeout - should timeout
|
||||||
|
bool locked = mtx.lock(50); // 50ms timeout
|
||||||
|
if (!locked) {
|
||||||
|
timeout_occurred.store(true);
|
||||||
|
} else {
|
||||||
|
mtx.unlock();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
mtx.unlock();
|
||||||
|
|
||||||
|
t.join();
|
||||||
|
|
||||||
|
// Timeout should have occurred since we held the lock
|
||||||
|
EXPECT_TRUE(timeout_occurred.load());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test reopen after close
|
||||||
|
TEST_F(MutexTest, ReopenAfterClose) {
|
||||||
|
std::string name = generate_unique_mutex_name("reopen");
|
||||||
|
|
||||||
|
mutex mtx;
|
||||||
|
|
||||||
|
ASSERT_TRUE(mtx.open(name.c_str()));
|
||||||
|
EXPECT_TRUE(mtx.valid());
|
||||||
|
|
||||||
|
mtx.close();
|
||||||
|
EXPECT_FALSE(mtx.valid());
|
||||||
|
|
||||||
|
ASSERT_TRUE(mtx.open(name.c_str()));
|
||||||
|
EXPECT_TRUE(mtx.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test named mutex inter-thread synchronization
|
||||||
|
TEST_F(MutexTest, NamedMutexInterThread) {
|
||||||
|
std::string name = generate_unique_mutex_name("inter_thread");
|
||||||
|
|
||||||
|
int shared_data = 0;
|
||||||
|
std::atomic<bool> t1_done{false};
|
||||||
|
std::atomic<bool> t2_done{false};
|
||||||
|
|
||||||
|
std::thread t1([&]() {
|
||||||
|
mutex mtx(name.c_str());
|
||||||
|
ASSERT_TRUE(mtx.valid());
|
||||||
|
|
||||||
|
mtx.lock();
|
||||||
|
shared_data = 100;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
mtx.unlock();
|
||||||
|
|
||||||
|
t1_done.store(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
std::thread t2([&]() {
|
||||||
|
// Wait a bit to ensure t1 starts first
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
|
||||||
|
mutex mtx(name.c_str());
|
||||||
|
ASSERT_TRUE(mtx.valid());
|
||||||
|
|
||||||
|
mtx.lock();
|
||||||
|
EXPECT_TRUE(t1_done.load() || shared_data == 100);
|
||||||
|
shared_data = 200;
|
||||||
|
mtx.unlock();
|
||||||
|
|
||||||
|
t2_done.store(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
t1.join();
|
||||||
|
t2.join();
|
||||||
|
|
||||||
|
EXPECT_EQ(shared_data, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test exception safety of try_lock
|
||||||
|
TEST_F(MutexTest, TryLockExceptionSafety) {
|
||||||
|
std::string name = generate_unique_mutex_name("try_lock_exception");
|
||||||
|
|
||||||
|
mutex mtx(name.c_str());
|
||||||
|
ASSERT_TRUE(mtx.valid());
|
||||||
|
|
||||||
|
bool exception_thrown = false;
|
||||||
|
try {
|
||||||
|
mtx.try_lock();
|
||||||
|
} catch (const std::system_error&) {
|
||||||
|
exception_thrown = true;
|
||||||
|
} catch (...) {
|
||||||
|
FAIL() << "Unexpected exception type";
|
||||||
|
}
|
||||||
|
|
||||||
|
// try_lock may throw system_error
|
||||||
|
// Just ensure we can handle it
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test concurrent open/close operations
|
||||||
|
TEST_F(MutexTest, ConcurrentOpenClose) {
|
||||||
|
std::vector<std::thread> threads;
|
||||||
|
std::atomic<int> success_count{0};
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
threads.emplace_back([&, i]() {
|
||||||
|
std::string name = generate_unique_mutex_name("concurrent");
|
||||||
|
name += std::to_string(i);
|
||||||
|
|
||||||
|
mutex mtx;
|
||||||
|
if (mtx.open(name.c_str())) {
|
||||||
|
++success_count;
|
||||||
|
mtx.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& t : threads) {
|
||||||
|
t.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_EQ(success_count.load(), 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test mutex with zero timeout
|
||||||
|
TEST_F(MutexTest, ZeroTimeout) {
|
||||||
|
std::string name = generate_unique_mutex_name("zero_timeout");
|
||||||
|
|
||||||
|
mutex mtx(name.c_str());
|
||||||
|
ASSERT_TRUE(mtx.valid());
|
||||||
|
|
||||||
|
// Lock with zero timeout (should try once and return)
|
||||||
|
bool locked = mtx.lock(0);
|
||||||
|
|
||||||
|
if (locked) {
|
||||||
|
mtx.unlock();
|
||||||
|
}
|
||||||
|
// Result may vary, just ensure it doesn't hang
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test rapid lock/unlock sequence
|
||||||
|
TEST_F(MutexTest, RapidLockUnlock) {
|
||||||
|
std::string name = generate_unique_mutex_name("rapid");
|
||||||
|
|
||||||
|
mutex mtx(name.c_str());
|
||||||
|
ASSERT_TRUE(mtx.valid());
|
||||||
|
|
||||||
|
auto rapid_task = [&]() {
|
||||||
|
for (int i = 0; i < 1000; ++i) {
|
||||||
|
mtx.lock();
|
||||||
|
mtx.unlock();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::thread t1(rapid_task);
|
||||||
|
std::thread t2(rapid_task);
|
||||||
|
|
||||||
|
t1.join();
|
||||||
|
t2.join();
|
||||||
|
|
||||||
|
// Should complete without deadlock or crash
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test lock after clear
|
||||||
|
TEST_F(MutexTest, LockAfterClear) {
|
||||||
|
std::string name = generate_unique_mutex_name("lock_after_clear");
|
||||||
|
|
||||||
|
mutex mtx(name.c_str());
|
||||||
|
ASSERT_TRUE(mtx.valid());
|
||||||
|
|
||||||
|
mtx.lock();
|
||||||
|
mtx.unlock();
|
||||||
|
|
||||||
|
mtx.clear();
|
||||||
|
EXPECT_FALSE(mtx.valid());
|
||||||
|
|
||||||
|
// Attempting to lock after clear should fail gracefully
|
||||||
|
bool locked = mtx.lock();
|
||||||
|
EXPECT_FALSE(locked);
|
||||||
|
}
|
||||||
487
test/test_semaphore.cpp
Normal file
487
test/test_semaphore.cpp
Normal file
@ -0,0 +1,487 @@
|
|||||||
|
/**
|
||||||
|
* @file test_semaphore.cpp
|
||||||
|
* @brief Comprehensive unit tests for ipc::sync::semaphore class
|
||||||
|
*
|
||||||
|
* This test suite covers:
|
||||||
|
* - Semaphore construction (default and named with count)
|
||||||
|
* - Wait and post operations
|
||||||
|
* - Timed wait with timeout
|
||||||
|
* - Named semaphore for inter-process synchronization
|
||||||
|
* - Resource cleanup (clear, clear_storage)
|
||||||
|
* - Producer-consumer patterns
|
||||||
|
* - Multiple wait/post scenarios
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
#include <atomic>
|
||||||
|
#include <vector>
|
||||||
|
#include "libipc/semaphore.h"
|
||||||
|
#include "libipc/def.h"
|
||||||
|
|
||||||
|
using namespace ipc;
|
||||||
|
using namespace ipc::sync;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string generate_unique_sem_name(const char* prefix) {
|
||||||
|
static int counter = 0;
|
||||||
|
return std::string(prefix) + "_sem_" + std::to_string(++counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
class SemaphoreTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void TearDown() override {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test default constructor
|
||||||
|
TEST_F(SemaphoreTest, DefaultConstructor) {
|
||||||
|
semaphore sem;
|
||||||
|
// Default constructed semaphore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test named constructor with count
|
||||||
|
TEST_F(SemaphoreTest, NamedConstructorWithCount) {
|
||||||
|
std::string name = generate_unique_sem_name("named_count");
|
||||||
|
|
||||||
|
semaphore sem(name.c_str(), 5);
|
||||||
|
EXPECT_TRUE(sem.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test named constructor with zero count
|
||||||
|
TEST_F(SemaphoreTest, NamedConstructorZeroCount) {
|
||||||
|
std::string name = generate_unique_sem_name("zero_count");
|
||||||
|
|
||||||
|
semaphore sem(name.c_str(), 0);
|
||||||
|
EXPECT_TRUE(sem.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test native() methods
|
||||||
|
TEST_F(SemaphoreTest, NativeHandle) {
|
||||||
|
std::string name = generate_unique_sem_name("native");
|
||||||
|
|
||||||
|
semaphore sem(name.c_str(), 1);
|
||||||
|
ASSERT_TRUE(sem.valid());
|
||||||
|
|
||||||
|
const void* const_handle = static_cast<const semaphore&>(sem).native();
|
||||||
|
void* handle = sem.native();
|
||||||
|
|
||||||
|
EXPECT_NE(const_handle, nullptr);
|
||||||
|
EXPECT_NE(handle, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test valid() method
|
||||||
|
TEST_F(SemaphoreTest, Valid) {
|
||||||
|
semaphore sem1;
|
||||||
|
|
||||||
|
std::string name = generate_unique_sem_name("valid");
|
||||||
|
semaphore sem2(name.c_str(), 1);
|
||||||
|
EXPECT_TRUE(sem2.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test open() method
|
||||||
|
TEST_F(SemaphoreTest, Open) {
|
||||||
|
std::string name = generate_unique_sem_name("open");
|
||||||
|
|
||||||
|
semaphore sem;
|
||||||
|
bool result = sem.open(name.c_str(), 3);
|
||||||
|
|
||||||
|
EXPECT_TRUE(result);
|
||||||
|
EXPECT_TRUE(sem.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test close() method
|
||||||
|
TEST_F(SemaphoreTest, Close) {
|
||||||
|
std::string name = generate_unique_sem_name("close");
|
||||||
|
|
||||||
|
semaphore sem(name.c_str(), 1);
|
||||||
|
ASSERT_TRUE(sem.valid());
|
||||||
|
|
||||||
|
sem.close();
|
||||||
|
EXPECT_FALSE(sem.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test clear() method
|
||||||
|
TEST_F(SemaphoreTest, Clear) {
|
||||||
|
std::string name = generate_unique_sem_name("clear");
|
||||||
|
|
||||||
|
semaphore sem(name.c_str(), 1);
|
||||||
|
ASSERT_TRUE(sem.valid());
|
||||||
|
|
||||||
|
sem.clear();
|
||||||
|
EXPECT_FALSE(sem.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test clear_storage() static method
|
||||||
|
TEST_F(SemaphoreTest, ClearStorage) {
|
||||||
|
std::string name = generate_unique_sem_name("clear_storage");
|
||||||
|
|
||||||
|
{
|
||||||
|
semaphore sem(name.c_str(), 1);
|
||||||
|
EXPECT_TRUE(sem.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
semaphore::clear_storage(name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test basic wait and post
|
||||||
|
TEST_F(SemaphoreTest, WaitPost) {
|
||||||
|
std::string name = generate_unique_sem_name("wait_post");
|
||||||
|
|
||||||
|
semaphore sem(name.c_str(), 1);
|
||||||
|
ASSERT_TRUE(sem.valid());
|
||||||
|
|
||||||
|
bool waited = sem.wait();
|
||||||
|
EXPECT_TRUE(waited);
|
||||||
|
|
||||||
|
bool posted = sem.post();
|
||||||
|
EXPECT_TRUE(posted);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test post with count
|
||||||
|
TEST_F(SemaphoreTest, PostWithCount) {
|
||||||
|
std::string name = generate_unique_sem_name("post_count");
|
||||||
|
|
||||||
|
semaphore sem(name.c_str(), 0);
|
||||||
|
ASSERT_TRUE(sem.valid());
|
||||||
|
|
||||||
|
bool posted = sem.post(5);
|
||||||
|
EXPECT_TRUE(posted);
|
||||||
|
|
||||||
|
// Now should be able to wait 5 times
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
EXPECT_TRUE(sem.wait(10)); // 10ms timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test timed wait with timeout
|
||||||
|
TEST_F(SemaphoreTest, TimedWait) {
|
||||||
|
std::string name = generate_unique_sem_name("timed_wait");
|
||||||
|
|
||||||
|
semaphore sem(name.c_str(), 1);
|
||||||
|
ASSERT_TRUE(sem.valid());
|
||||||
|
|
||||||
|
bool waited = sem.wait(100); // 100ms timeout
|
||||||
|
EXPECT_TRUE(waited);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test wait timeout scenario
|
||||||
|
TEST_F(SemaphoreTest, WaitTimeout) {
|
||||||
|
std::string name = generate_unique_sem_name("wait_timeout");
|
||||||
|
|
||||||
|
semaphore sem(name.c_str(), 0); // Zero count
|
||||||
|
ASSERT_TRUE(sem.valid());
|
||||||
|
|
||||||
|
auto start = std::chrono::steady_clock::now();
|
||||||
|
bool waited = sem.wait(50); // 50ms timeout
|
||||||
|
auto end = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
|
||||||
|
|
||||||
|
// Should timeout
|
||||||
|
EXPECT_FALSE(waited);
|
||||||
|
EXPECT_GE(elapsed, 40); // Allow some tolerance
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test infinite wait
|
||||||
|
TEST_F(SemaphoreTest, InfiniteWait) {
|
||||||
|
std::string name = generate_unique_sem_name("infinite_wait");
|
||||||
|
|
||||||
|
semaphore sem(name.c_str(), 0);
|
||||||
|
ASSERT_TRUE(sem.valid());
|
||||||
|
|
||||||
|
std::atomic<bool> wait_started{false};
|
||||||
|
std::atomic<bool> wait_succeeded{false};
|
||||||
|
|
||||||
|
std::thread waiter([&]() {
|
||||||
|
wait_started.store(true);
|
||||||
|
bool result = sem.wait(invalid_value);
|
||||||
|
wait_succeeded.store(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for thread to start waiting
|
||||||
|
while (!wait_started.load()) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
|
||||||
|
// Post to release the waiter
|
||||||
|
sem.post();
|
||||||
|
|
||||||
|
waiter.join();
|
||||||
|
|
||||||
|
EXPECT_TRUE(wait_succeeded.load());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test producer-consumer pattern
|
||||||
|
TEST_F(SemaphoreTest, ProducerConsumer) {
|
||||||
|
std::string name = generate_unique_sem_name("prod_cons");
|
||||||
|
|
||||||
|
semaphore sem(name.c_str(), 0);
|
||||||
|
ASSERT_TRUE(sem.valid());
|
||||||
|
|
||||||
|
std::atomic<int> produced{0};
|
||||||
|
std::atomic<int> consumed{0};
|
||||||
|
const int count = 10;
|
||||||
|
|
||||||
|
std::thread producer([&]() {
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
++produced;
|
||||||
|
sem.post();
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
std::thread consumer([&]() {
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
sem.wait();
|
||||||
|
++consumed;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
producer.join();
|
||||||
|
consumer.join();
|
||||||
|
|
||||||
|
EXPECT_EQ(produced.load(), count);
|
||||||
|
EXPECT_EQ(consumed.load(), count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test multiple producers and consumers
|
||||||
|
TEST_F(SemaphoreTest, MultipleProducersConsumers) {
|
||||||
|
std::string name = generate_unique_sem_name("multi_prod_cons");
|
||||||
|
|
||||||
|
semaphore sem(name.c_str(), 0);
|
||||||
|
ASSERT_TRUE(sem.valid());
|
||||||
|
|
||||||
|
std::atomic<int> total_produced{0};
|
||||||
|
std::atomic<int> total_consumed{0};
|
||||||
|
const int items_per_producer = 5;
|
||||||
|
const int num_producers = 3;
|
||||||
|
const int num_consumers = 3;
|
||||||
|
|
||||||
|
std::vector<std::thread> producers;
|
||||||
|
for (int i = 0; i < num_producers; ++i) {
|
||||||
|
producers.emplace_back([&]() {
|
||||||
|
for (int j = 0; j < items_per_producer; ++j) {
|
||||||
|
++total_produced;
|
||||||
|
sem.post();
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::thread> consumers;
|
||||||
|
for (int i = 0; i < num_consumers; ++i) {
|
||||||
|
consumers.emplace_back([&]() {
|
||||||
|
for (int j = 0; j < items_per_producer; ++j) {
|
||||||
|
if (sem.wait(1000)) {
|
||||||
|
++total_consumed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& t : producers) t.join();
|
||||||
|
for (auto& t : consumers) t.join();
|
||||||
|
|
||||||
|
EXPECT_EQ(total_produced.load(), items_per_producer * num_producers);
|
||||||
|
EXPECT_EQ(total_consumed.load(), items_per_producer * num_producers);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test semaphore with initial count
|
||||||
|
TEST_F(SemaphoreTest, InitialCount) {
|
||||||
|
std::string name = generate_unique_sem_name("initial_count");
|
||||||
|
const uint32_t initial = 3;
|
||||||
|
|
||||||
|
semaphore sem(name.c_str(), initial);
|
||||||
|
ASSERT_TRUE(sem.valid());
|
||||||
|
|
||||||
|
// Should be able to wait 'initial' times without blocking
|
||||||
|
for (uint32_t i = 0; i < initial; ++i) {
|
||||||
|
EXPECT_TRUE(sem.wait(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next wait should timeout
|
||||||
|
EXPECT_FALSE(sem.wait(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test rapid post operations
|
||||||
|
TEST_F(SemaphoreTest, RapidPost) {
|
||||||
|
std::string name = generate_unique_sem_name("rapid_post");
|
||||||
|
|
||||||
|
semaphore sem(name.c_str(), 0);
|
||||||
|
ASSERT_TRUE(sem.valid());
|
||||||
|
|
||||||
|
const int post_count = 100;
|
||||||
|
for (int i = 0; i < post_count; ++i) {
|
||||||
|
EXPECT_TRUE(sem.post());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be able to wait post_count times
|
||||||
|
int wait_count = 0;
|
||||||
|
for (int i = 0; i < post_count; ++i) {
|
||||||
|
if (sem.wait(10)) {
|
||||||
|
++wait_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_EQ(wait_count, post_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test concurrent post operations
|
||||||
|
TEST_F(SemaphoreTest, ConcurrentPost) {
|
||||||
|
std::string name = generate_unique_sem_name("concurrent_post");
|
||||||
|
|
||||||
|
semaphore sem(name.c_str(), 0);
|
||||||
|
ASSERT_TRUE(sem.valid());
|
||||||
|
|
||||||
|
std::atomic<int> post_count{0};
|
||||||
|
const int threads = 5;
|
||||||
|
const int posts_per_thread = 10;
|
||||||
|
|
||||||
|
std::vector<std::thread> posters;
|
||||||
|
for (int i = 0; i < threads; ++i) {
|
||||||
|
posters.emplace_back([&]() {
|
||||||
|
for (int j = 0; j < posts_per_thread; ++j) {
|
||||||
|
if (sem.post()) {
|
||||||
|
++post_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& t : posters) t.join();
|
||||||
|
|
||||||
|
EXPECT_EQ(post_count.load(), threads * posts_per_thread);
|
||||||
|
|
||||||
|
// Verify by consuming
|
||||||
|
int consumed = 0;
|
||||||
|
for (int i = 0; i < threads * posts_per_thread; ++i) {
|
||||||
|
if (sem.wait(10)) {
|
||||||
|
++consumed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_EQ(consumed, threads * posts_per_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test reopen after close
|
||||||
|
TEST_F(SemaphoreTest, ReopenAfterClose) {
|
||||||
|
std::string name = generate_unique_sem_name("reopen");
|
||||||
|
|
||||||
|
semaphore sem;
|
||||||
|
|
||||||
|
ASSERT_TRUE(sem.open(name.c_str(), 2));
|
||||||
|
EXPECT_TRUE(sem.valid());
|
||||||
|
|
||||||
|
sem.close();
|
||||||
|
EXPECT_FALSE(sem.valid());
|
||||||
|
|
||||||
|
ASSERT_TRUE(sem.open(name.c_str(), 3));
|
||||||
|
EXPECT_TRUE(sem.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test named semaphore sharing between threads
|
||||||
|
TEST_F(SemaphoreTest, NamedSemaphoreSharing) {
|
||||||
|
std::string name = generate_unique_sem_name("sharing");
|
||||||
|
|
||||||
|
std::atomic<int> value{0};
|
||||||
|
|
||||||
|
std::thread t1([&]() {
|
||||||
|
semaphore sem(name.c_str(), 0);
|
||||||
|
ASSERT_TRUE(sem.valid());
|
||||||
|
|
||||||
|
sem.wait(); // Wait for signal
|
||||||
|
value.store(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
std::thread t2([&]() {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
|
||||||
|
semaphore sem(name.c_str(), 0);
|
||||||
|
ASSERT_TRUE(sem.valid());
|
||||||
|
|
||||||
|
sem.post(); // Signal t1
|
||||||
|
});
|
||||||
|
|
||||||
|
t1.join();
|
||||||
|
t2.join();
|
||||||
|
|
||||||
|
EXPECT_EQ(value.load(), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test post multiple count at once
|
||||||
|
TEST_F(SemaphoreTest, PostMultiple) {
|
||||||
|
std::string name = generate_unique_sem_name("post_multiple");
|
||||||
|
|
||||||
|
semaphore sem(name.c_str(), 0);
|
||||||
|
ASSERT_TRUE(sem.valid());
|
||||||
|
|
||||||
|
const uint32_t count = 10;
|
||||||
|
bool posted = sem.post(count);
|
||||||
|
EXPECT_TRUE(posted);
|
||||||
|
|
||||||
|
// Consume all
|
||||||
|
for (uint32_t i = 0; i < count; ++i) {
|
||||||
|
EXPECT_TRUE(sem.wait(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be empty now
|
||||||
|
EXPECT_FALSE(sem.wait(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test semaphore after clear
|
||||||
|
TEST_F(SemaphoreTest, AfterClear) {
|
||||||
|
std::string name = generate_unique_sem_name("after_clear");
|
||||||
|
|
||||||
|
semaphore sem(name.c_str(), 5);
|
||||||
|
ASSERT_TRUE(sem.valid());
|
||||||
|
|
||||||
|
sem.wait();
|
||||||
|
sem.clear();
|
||||||
|
EXPECT_FALSE(sem.valid());
|
||||||
|
|
||||||
|
// Operations after clear should fail gracefully
|
||||||
|
EXPECT_FALSE(sem.wait(10));
|
||||||
|
EXPECT_FALSE(sem.post());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test zero timeout
|
||||||
|
TEST_F(SemaphoreTest, ZeroTimeout) {
|
||||||
|
std::string name = generate_unique_sem_name("zero_timeout");
|
||||||
|
|
||||||
|
semaphore sem(name.c_str(), 0);
|
||||||
|
ASSERT_TRUE(sem.valid());
|
||||||
|
|
||||||
|
bool waited = sem.wait(0);
|
||||||
|
// Should return immediately (either success or timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test high-frequency wait/post
|
||||||
|
TEST_F(SemaphoreTest, HighFrequency) {
|
||||||
|
std::string name = generate_unique_sem_name("high_freq");
|
||||||
|
|
||||||
|
semaphore sem(name.c_str(), 0);
|
||||||
|
ASSERT_TRUE(sem.valid());
|
||||||
|
|
||||||
|
std::thread poster([&]() {
|
||||||
|
for (int i = 0; i < 1000; ++i) {
|
||||||
|
sem.post();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
std::thread waiter([&]() {
|
||||||
|
for (int i = 0; i < 1000; ++i) {
|
||||||
|
sem.wait(100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
poster.join();
|
||||||
|
waiter.join();
|
||||||
|
}
|
||||||
623
test/test_shm.cpp
Executable file → Normal file
623
test/test_shm.cpp
Executable file → Normal file
@ -1,102 +1,521 @@
|
|||||||
#include <cstring>
|
/**
|
||||||
#include <cstdint>
|
* @file test_shm.cpp
|
||||||
#include <thread>
|
* @brief Comprehensive unit tests for ipc::shm (shared memory) functionality
|
||||||
|
*
|
||||||
#include "libipc/shm.h"
|
* This test suite covers:
|
||||||
#include "test.h"
|
* - Low-level shared memory functions (acquire, get_mem, release, remove)
|
||||||
|
* - Reference counting (get_ref, sub_ref)
|
||||||
using namespace ipc::shm;
|
* - High-level handle class interface
|
||||||
|
* - Create and open modes
|
||||||
namespace {
|
* - Resource cleanup and error handling
|
||||||
|
*/
|
||||||
TEST(SHM, acquire) {
|
|
||||||
handle shm_hd;
|
#include <gtest/gtest.h>
|
||||||
EXPECT_FALSE(shm_hd.valid());
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
EXPECT_TRUE(shm_hd.acquire("my-test-1", 1024));
|
#include <string>
|
||||||
EXPECT_TRUE(shm_hd.valid());
|
#include "libipc/shm.h"
|
||||||
EXPECT_STREQ(shm_hd.name(), "my-test-1");
|
|
||||||
|
using namespace ipc;
|
||||||
EXPECT_TRUE(shm_hd.acquire("my-test-2", 2048));
|
|
||||||
EXPECT_TRUE(shm_hd.valid());
|
namespace {
|
||||||
EXPECT_STREQ(shm_hd.name(), "my-test-2");
|
|
||||||
|
// Generate unique shared memory names for tests
|
||||||
EXPECT_TRUE(shm_hd.acquire("my-test-3", 4096));
|
std::string generate_unique_name(const char* prefix) {
|
||||||
EXPECT_TRUE(shm_hd.valid());
|
static int counter = 0;
|
||||||
EXPECT_STREQ(shm_hd.name(), "my-test-3");
|
return std::string(prefix) + "_test_" + std::to_string(++counter);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SHM, release) {
|
} // anonymous namespace
|
||||||
handle shm_hd;
|
|
||||||
EXPECT_FALSE(shm_hd.valid());
|
class ShmTest : public ::testing::Test {
|
||||||
shm_hd.release();
|
protected:
|
||||||
EXPECT_FALSE(shm_hd.valid());
|
void TearDown() override {
|
||||||
EXPECT_TRUE(shm_hd.acquire("release-test-1", 512));
|
// Clean up any leftover shared memory segments
|
||||||
EXPECT_TRUE(shm_hd.valid());
|
}
|
||||||
shm_hd.release();
|
};
|
||||||
EXPECT_FALSE(shm_hd.valid());
|
|
||||||
}
|
// ========== Low-level API Tests ==========
|
||||||
|
|
||||||
TEST(SHM, get) {
|
// Test acquire with create mode
|
||||||
handle shm_hd;
|
TEST_F(ShmTest, AcquireCreate) {
|
||||||
EXPECT_TRUE(shm_hd.get() == nullptr);
|
std::string name = generate_unique_name("acquire_create");
|
||||||
EXPECT_TRUE(shm_hd.acquire("get-test", 2048));
|
const std::size_t size = 1024;
|
||||||
|
|
||||||
auto mem = shm_hd.get();
|
shm::id_t id = shm::acquire(name.c_str(), size, shm::create);
|
||||||
EXPECT_TRUE(mem != nullptr);
|
ASSERT_NE(id, nullptr);
|
||||||
EXPECT_TRUE(mem == shm_hd.get());
|
|
||||||
|
std::size_t actual_size = 0;
|
||||||
std::uint8_t buf[1024] = {};
|
void* mem = shm::get_mem(id, &actual_size);
|
||||||
EXPECT_TRUE(memcmp(mem, buf, sizeof(buf)) == 0);
|
EXPECT_NE(mem, nullptr);
|
||||||
|
EXPECT_GE(actual_size, size);
|
||||||
handle shm_other(shm_hd.name(), shm_hd.size());
|
|
||||||
EXPECT_TRUE(shm_other.get() != shm_hd.get());
|
// Use remove(id) to clean up - it internally calls release()
|
||||||
}
|
shm::remove(id);
|
||||||
|
}
|
||||||
TEST(SHM, hello) {
|
|
||||||
handle shm_hd;
|
// Test acquire with open mode (should fail if not exists)
|
||||||
EXPECT_TRUE(shm_hd.acquire("hello-test", 128));
|
TEST_F(ShmTest, AcquireOpenNonExistent) {
|
||||||
auto mem = shm_hd.get();
|
std::string name = generate_unique_name("acquire_open_fail");
|
||||||
EXPECT_TRUE(mem != nullptr);
|
|
||||||
|
shm::id_t id = shm::acquire(name.c_str(), 1024, shm::open);
|
||||||
constexpr char hello[] = "hello!";
|
// Opening non-existent shared memory should return nullptr or handle failure gracefully
|
||||||
std::memcpy(mem, hello, sizeof(hello));
|
if (id != nullptr) {
|
||||||
EXPECT_STREQ((char const *)shm_hd.get(), hello);
|
shm::release(id);
|
||||||
|
}
|
||||||
shm_hd.release();
|
}
|
||||||
EXPECT_TRUE(shm_hd.get() == nullptr);
|
|
||||||
EXPECT_TRUE(shm_hd.acquire("hello-test", 1024));
|
// Test acquire with both create and open modes
|
||||||
|
TEST_F(ShmTest, AcquireCreateOrOpen) {
|
||||||
mem = shm_hd.get();
|
std::string name = generate_unique_name("acquire_both");
|
||||||
EXPECT_TRUE(mem != nullptr);
|
const std::size_t size = 2048;
|
||||||
std::uint8_t buf[1024] = {};
|
|
||||||
EXPECT_TRUE(memcmp(mem, buf, sizeof(buf)) == 0);
|
shm::id_t id = shm::acquire(name.c_str(), size, shm::create | shm::open);
|
||||||
|
ASSERT_NE(id, nullptr);
|
||||||
std::memcpy(mem, hello, sizeof(hello));
|
|
||||||
EXPECT_STREQ((char const *)shm_hd.get(), hello);
|
std::size_t actual_size = 0;
|
||||||
}
|
void* mem = shm::get_mem(id, &actual_size);
|
||||||
|
EXPECT_NE(mem, nullptr);
|
||||||
TEST(SHM, mt) {
|
EXPECT_GE(actual_size, size);
|
||||||
handle shm_hd;
|
|
||||||
EXPECT_TRUE(shm_hd.acquire("mt-test", 256));
|
// Use remove(id) to clean up - it internally calls release()
|
||||||
constexpr char hello[] = "hello!";
|
shm::remove(id);
|
||||||
std::memcpy(shm_hd.get(), hello, sizeof(hello));
|
}
|
||||||
|
|
||||||
std::thread {
|
// Test get_mem function
|
||||||
[&shm_hd] {
|
TEST_F(ShmTest, GetMemory) {
|
||||||
handle shm_mt(shm_hd.name(), shm_hd.size());
|
std::string name = generate_unique_name("get_mem");
|
||||||
shm_hd.release();
|
const std::size_t size = 512;
|
||||||
constexpr char hello[] = "hello!";
|
|
||||||
EXPECT_STREQ((char const *)shm_mt.get(), hello);
|
shm::id_t id = shm::acquire(name.c_str(), size, shm::create);
|
||||||
}
|
ASSERT_NE(id, nullptr);
|
||||||
}.join();
|
|
||||||
|
std::size_t returned_size = 0;
|
||||||
EXPECT_TRUE(shm_hd.get() == nullptr);
|
void* mem = shm::get_mem(id, &returned_size);
|
||||||
EXPECT_FALSE(shm_hd.valid());
|
|
||||||
|
EXPECT_NE(mem, nullptr);
|
||||||
EXPECT_TRUE(shm_hd.acquire("mt-test", 1024));
|
EXPECT_GE(returned_size, size);
|
||||||
std::uint8_t buf[1024] = {};
|
|
||||||
EXPECT_TRUE(memcmp(shm_hd.get(), buf, sizeof(buf)) == 0);
|
// Write and read test data
|
||||||
}
|
const char* test_data = "Shared memory test data";
|
||||||
|
std::strcpy(static_cast<char*>(mem), test_data);
|
||||||
} // internal-linkage
|
EXPECT_STREQ(static_cast<char*>(mem), test_data);
|
||||||
|
|
||||||
|
// Use remove(id) to clean up - it internally calls release()
|
||||||
|
shm::remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test get_mem without size parameter
|
||||||
|
TEST_F(ShmTest, GetMemoryNoSize) {
|
||||||
|
std::string name = generate_unique_name("get_mem_no_size");
|
||||||
|
|
||||||
|
shm::id_t id = shm::acquire(name.c_str(), 256, shm::create);
|
||||||
|
ASSERT_NE(id, nullptr);
|
||||||
|
|
||||||
|
void* mem = shm::get_mem(id, nullptr);
|
||||||
|
EXPECT_NE(mem, nullptr);
|
||||||
|
|
||||||
|
// Use remove(id) to clean up - it internally calls release()
|
||||||
|
shm::remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test release function
|
||||||
|
TEST_F(ShmTest, ReleaseMemory) {
|
||||||
|
std::string name = generate_unique_name("release");
|
||||||
|
|
||||||
|
shm::id_t id = shm::acquire(name.c_str(), 128, shm::create);
|
||||||
|
ASSERT_NE(id, nullptr);
|
||||||
|
|
||||||
|
// Must call get_mem to map memory and set reference count
|
||||||
|
void* mem = shm::get_mem(id, nullptr);
|
||||||
|
ASSERT_NE(mem, nullptr);
|
||||||
|
|
||||||
|
// release returns the reference count before decrement, or -1 on error
|
||||||
|
std::int32_t ref_count = shm::release(id);
|
||||||
|
EXPECT_EQ(ref_count, 1); // Should be 1 (set by get_mem, before decrement)
|
||||||
|
|
||||||
|
shm::remove(name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test remove by id
|
||||||
|
TEST_F(ShmTest, RemoveById) {
|
||||||
|
std::string name = generate_unique_name("remove_by_id");
|
||||||
|
|
||||||
|
shm::id_t id = shm::acquire(name.c_str(), 256, shm::create);
|
||||||
|
ASSERT_NE(id, nullptr);
|
||||||
|
|
||||||
|
// remove(id) internally calls release(id), so we don't need to call release first
|
||||||
|
shm::remove(id); // Should succeed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test remove by name
|
||||||
|
TEST_F(ShmTest, RemoveByName) {
|
||||||
|
std::string name = generate_unique_name("remove_by_name");
|
||||||
|
|
||||||
|
shm::id_t id = shm::acquire(name.c_str(), 256, shm::create);
|
||||||
|
ASSERT_NE(id, nullptr);
|
||||||
|
|
||||||
|
shm::release(id);
|
||||||
|
shm::remove(name.c_str()); // Should succeed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test reference counting
|
||||||
|
TEST_F(ShmTest, ReferenceCount) {
|
||||||
|
std::string name = generate_unique_name("ref_count");
|
||||||
|
|
||||||
|
shm::id_t id1 = shm::acquire(name.c_str(), 512, shm::create);
|
||||||
|
ASSERT_NE(id1, nullptr);
|
||||||
|
|
||||||
|
// Reference count is 0 after acquire (memory not mapped yet)
|
||||||
|
std::int32_t ref_before_get_mem = shm::get_ref(id1);
|
||||||
|
EXPECT_EQ(ref_before_get_mem, 0);
|
||||||
|
|
||||||
|
// get_mem maps memory and sets reference count to 1
|
||||||
|
void* mem1 = shm::get_mem(id1, nullptr);
|
||||||
|
ASSERT_NE(mem1, nullptr);
|
||||||
|
|
||||||
|
std::int32_t ref1 = shm::get_ref(id1);
|
||||||
|
EXPECT_EQ(ref1, 1);
|
||||||
|
|
||||||
|
// Acquire again and get_mem (should increase reference count)
|
||||||
|
shm::id_t id2 = shm::acquire(name.c_str(), 512, shm::open);
|
||||||
|
if (id2 != nullptr) {
|
||||||
|
void* mem2 = shm::get_mem(id2, nullptr);
|
||||||
|
ASSERT_NE(mem2, nullptr);
|
||||||
|
|
||||||
|
std::int32_t ref2 = shm::get_ref(id2);
|
||||||
|
EXPECT_EQ(ref2, 2); // Should be 2 now
|
||||||
|
|
||||||
|
shm::release(id2);
|
||||||
|
}
|
||||||
|
|
||||||
|
shm::release(id1);
|
||||||
|
shm::remove(name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test sub_ref function
|
||||||
|
TEST_F(ShmTest, SubtractReference) {
|
||||||
|
std::string name = generate_unique_name("sub_ref");
|
||||||
|
|
||||||
|
shm::id_t id = shm::acquire(name.c_str(), 256, shm::create);
|
||||||
|
ASSERT_NE(id, nullptr);
|
||||||
|
|
||||||
|
// Must call get_mem first to map memory and initialize reference count
|
||||||
|
void* mem = shm::get_mem(id, nullptr);
|
||||||
|
ASSERT_NE(mem, nullptr);
|
||||||
|
|
||||||
|
std::int32_t ref_before = shm::get_ref(id);
|
||||||
|
EXPECT_EQ(ref_before, 1); // Should be 1 after get_mem
|
||||||
|
|
||||||
|
shm::sub_ref(id);
|
||||||
|
|
||||||
|
std::int32_t ref_after = shm::get_ref(id);
|
||||||
|
EXPECT_EQ(ref_after, 0); // Should be 0 after sub_ref
|
||||||
|
|
||||||
|
// Use remove(id) to clean up - it internally calls release()
|
||||||
|
shm::remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== High-level handle class Tests ==========
|
||||||
|
|
||||||
|
// Test default handle constructor
|
||||||
|
TEST_F(ShmTest, HandleDefaultConstructor) {
|
||||||
|
shm::handle h;
|
||||||
|
EXPECT_FALSE(h.valid());
|
||||||
|
EXPECT_EQ(h.size(), 0u);
|
||||||
|
EXPECT_EQ(h.get(), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test handle constructor with name and size
|
||||||
|
TEST_F(ShmTest, HandleConstructorWithParams) {
|
||||||
|
std::string name = generate_unique_name("handle_ctor");
|
||||||
|
const std::size_t size = 1024;
|
||||||
|
|
||||||
|
shm::handle h(name.c_str(), size);
|
||||||
|
|
||||||
|
EXPECT_TRUE(h.valid());
|
||||||
|
EXPECT_GE(h.size(), size);
|
||||||
|
EXPECT_NE(h.get(), nullptr);
|
||||||
|
EXPECT_STREQ(h.name(), name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test handle move constructor
|
||||||
|
TEST_F(ShmTest, HandleMoveConstructor) {
|
||||||
|
std::string name = generate_unique_name("handle_move");
|
||||||
|
|
||||||
|
shm::handle h1(name.c_str(), 512);
|
||||||
|
ASSERT_TRUE(h1.valid());
|
||||||
|
|
||||||
|
void* ptr1 = h1.get();
|
||||||
|
std::size_t size1 = h1.size();
|
||||||
|
|
||||||
|
shm::handle h2(std::move(h1));
|
||||||
|
|
||||||
|
EXPECT_TRUE(h2.valid());
|
||||||
|
EXPECT_EQ(h2.get(), ptr1);
|
||||||
|
EXPECT_EQ(h2.size(), size1);
|
||||||
|
|
||||||
|
// h1 should be invalid after move
|
||||||
|
EXPECT_FALSE(h1.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test handle swap
|
||||||
|
TEST_F(ShmTest, HandleSwap) {
|
||||||
|
std::string name1 = generate_unique_name("handle_swap1");
|
||||||
|
std::string name2 = generate_unique_name("handle_swap2");
|
||||||
|
|
||||||
|
shm::handle h1(name1.c_str(), 256);
|
||||||
|
shm::handle h2(name2.c_str(), 512);
|
||||||
|
|
||||||
|
void* ptr1 = h1.get();
|
||||||
|
void* ptr2 = h2.get();
|
||||||
|
std::size_t size1 = h1.size();
|
||||||
|
std::size_t size2 = h2.size();
|
||||||
|
|
||||||
|
h1.swap(h2);
|
||||||
|
|
||||||
|
EXPECT_EQ(h1.get(), ptr2);
|
||||||
|
EXPECT_EQ(h1.size(), size2);
|
||||||
|
EXPECT_EQ(h2.get(), ptr1);
|
||||||
|
EXPECT_EQ(h2.size(), size1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test handle assignment operator
|
||||||
|
TEST_F(ShmTest, HandleAssignment) {
|
||||||
|
std::string name = generate_unique_name("handle_assign");
|
||||||
|
|
||||||
|
shm::handle h1(name.c_str(), 768);
|
||||||
|
void* ptr1 = h1.get();
|
||||||
|
|
||||||
|
shm::handle h2;
|
||||||
|
h2 = std::move(h1);
|
||||||
|
|
||||||
|
EXPECT_TRUE(h2.valid());
|
||||||
|
EXPECT_EQ(h2.get(), ptr1);
|
||||||
|
EXPECT_FALSE(h1.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test handle valid() method
|
||||||
|
TEST_F(ShmTest, HandleValid) {
|
||||||
|
shm::handle h1;
|
||||||
|
EXPECT_FALSE(h1.valid());
|
||||||
|
|
||||||
|
std::string name = generate_unique_name("handle_valid");
|
||||||
|
shm::handle h2(name.c_str(), 128);
|
||||||
|
EXPECT_TRUE(h2.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test handle size() method
|
||||||
|
TEST_F(ShmTest, HandleSize) {
|
||||||
|
std::string name = generate_unique_name("handle_size");
|
||||||
|
const std::size_t requested_size = 2048;
|
||||||
|
|
||||||
|
shm::handle h(name.c_str(), requested_size);
|
||||||
|
|
||||||
|
EXPECT_GE(h.size(), requested_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test handle name() method
|
||||||
|
TEST_F(ShmTest, HandleName) {
|
||||||
|
std::string name = generate_unique_name("handle_name");
|
||||||
|
|
||||||
|
shm::handle h(name.c_str(), 256);
|
||||||
|
|
||||||
|
EXPECT_STREQ(h.name(), name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test handle ref() method
|
||||||
|
TEST_F(ShmTest, HandleRef) {
|
||||||
|
std::string name = generate_unique_name("handle_ref");
|
||||||
|
|
||||||
|
shm::handle h(name.c_str(), 256);
|
||||||
|
|
||||||
|
std::int32_t ref = h.ref();
|
||||||
|
EXPECT_GT(ref, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test handle sub_ref() method
|
||||||
|
TEST_F(ShmTest, HandleSubRef) {
|
||||||
|
std::string name = generate_unique_name("handle_sub_ref");
|
||||||
|
|
||||||
|
shm::handle h(name.c_str(), 256);
|
||||||
|
|
||||||
|
std::int32_t ref_before = h.ref();
|
||||||
|
h.sub_ref();
|
||||||
|
std::int32_t ref_after = h.ref();
|
||||||
|
|
||||||
|
EXPECT_EQ(ref_after, ref_before - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test handle acquire() method
|
||||||
|
TEST_F(ShmTest, HandleAcquire) {
|
||||||
|
shm::handle h;
|
||||||
|
EXPECT_FALSE(h.valid());
|
||||||
|
|
||||||
|
std::string name = generate_unique_name("handle_acquire");
|
||||||
|
bool result = h.acquire(name.c_str(), 512);
|
||||||
|
|
||||||
|
EXPECT_TRUE(result);
|
||||||
|
EXPECT_TRUE(h.valid());
|
||||||
|
EXPECT_GE(h.size(), 512u);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test handle release() method
|
||||||
|
TEST_F(ShmTest, HandleRelease) {
|
||||||
|
std::string name = generate_unique_name("handle_release");
|
||||||
|
|
||||||
|
shm::handle h(name.c_str(), 256);
|
||||||
|
ASSERT_TRUE(h.valid());
|
||||||
|
|
||||||
|
std::int32_t ref_count = h.release();
|
||||||
|
EXPECT_GE(ref_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test handle clear() method
|
||||||
|
TEST_F(ShmTest, HandleClear) {
|
||||||
|
std::string name = generate_unique_name("handle_clear");
|
||||||
|
|
||||||
|
shm::handle h(name.c_str(), 256);
|
||||||
|
ASSERT_TRUE(h.valid());
|
||||||
|
|
||||||
|
h.clear();
|
||||||
|
EXPECT_FALSE(h.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test handle clear_storage() static method
|
||||||
|
TEST_F(ShmTest, HandleClearStorage) {
|
||||||
|
std::string name = generate_unique_name("handle_clear_storage");
|
||||||
|
|
||||||
|
{
|
||||||
|
shm::handle h(name.c_str(), 256);
|
||||||
|
EXPECT_TRUE(h.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
shm::handle::clear_storage(name.c_str());
|
||||||
|
|
||||||
|
// Try to open - should fail or create new
|
||||||
|
shm::handle h2(name.c_str(), 256, shm::open);
|
||||||
|
// Behavior depends on implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test handle get() method
|
||||||
|
TEST_F(ShmTest, HandleGet) {
|
||||||
|
std::string name = generate_unique_name("handle_get");
|
||||||
|
|
||||||
|
shm::handle h(name.c_str(), 512);
|
||||||
|
|
||||||
|
void* mem = h.get();
|
||||||
|
EXPECT_NE(mem, nullptr);
|
||||||
|
|
||||||
|
// Write and read test
|
||||||
|
const char* test_str = "Handle get test";
|
||||||
|
std::strcpy(static_cast<char*>(mem), test_str);
|
||||||
|
EXPECT_STREQ(static_cast<char*>(mem), test_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test handle detach() and attach() methods
|
||||||
|
TEST_F(ShmTest, HandleDetachAttach) {
|
||||||
|
std::string name = generate_unique_name("handle_detach_attach");
|
||||||
|
|
||||||
|
shm::handle h1(name.c_str(), 256);
|
||||||
|
ASSERT_TRUE(h1.valid());
|
||||||
|
|
||||||
|
shm::id_t id = h1.detach();
|
||||||
|
EXPECT_NE(id, nullptr);
|
||||||
|
EXPECT_FALSE(h1.valid()); // Should be invalid after detach
|
||||||
|
|
||||||
|
shm::handle h2;
|
||||||
|
h2.attach(id);
|
||||||
|
EXPECT_TRUE(h2.valid());
|
||||||
|
|
||||||
|
// Clean up - use h2.clear() or shm::remove(id) alone, not both
|
||||||
|
// Option 1: Use handle's clear() which calls shm::remove(id) internally
|
||||||
|
id = h2.detach(); // Detach first to get the id without releasing
|
||||||
|
shm::remove(id); // Then remove to clean up both memory and disk file
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test writing and reading data through shared memory
|
||||||
|
TEST_F(ShmTest, WriteReadData) {
|
||||||
|
std::string name = generate_unique_name("write_read");
|
||||||
|
const std::size_t size = 1024;
|
||||||
|
|
||||||
|
shm::handle h1(name.c_str(), size);
|
||||||
|
ASSERT_TRUE(h1.valid());
|
||||||
|
|
||||||
|
// Write test data
|
||||||
|
struct TestData {
|
||||||
|
int value;
|
||||||
|
char text[64];
|
||||||
|
};
|
||||||
|
|
||||||
|
TestData* data1 = static_cast<TestData*>(h1.get());
|
||||||
|
data1->value = 42;
|
||||||
|
std::strcpy(data1->text, "Shared memory data");
|
||||||
|
|
||||||
|
// Open in another "shm::handle" (simulating different process)
|
||||||
|
shm::handle h2(name.c_str(), size, shm::open);
|
||||||
|
if (h2.valid()) {
|
||||||
|
TestData* data2 = static_cast<TestData*>(h2.get());
|
||||||
|
EXPECT_EQ(data2->value, 42);
|
||||||
|
EXPECT_STREQ(data2->text, "Shared memory data");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test handle with different modes
|
||||||
|
TEST_F(ShmTest, HandleModes) {
|
||||||
|
std::string name = generate_unique_name("handle_modes");
|
||||||
|
|
||||||
|
// Create only
|
||||||
|
shm::handle h1(name.c_str(), 256, shm::create);
|
||||||
|
EXPECT_TRUE(h1.valid());
|
||||||
|
|
||||||
|
// Open existing
|
||||||
|
shm::handle h2(name.c_str(), 256, shm::open);
|
||||||
|
EXPECT_TRUE(h2.valid());
|
||||||
|
|
||||||
|
// Both modes
|
||||||
|
shm::handle h3(name.c_str(), 256, shm::create | shm::open);
|
||||||
|
EXPECT_TRUE(h3.valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test multiple handles to same shared memory
|
||||||
|
TEST_F(ShmTest, MultipleHandles) {
|
||||||
|
std::string name = generate_unique_name("multiple_handles");
|
||||||
|
const std::size_t size = 512;
|
||||||
|
|
||||||
|
shm::handle h1(name.c_str(), size);
|
||||||
|
shm::handle h2(name.c_str(), size, shm::open);
|
||||||
|
|
||||||
|
ASSERT_TRUE(h1.valid());
|
||||||
|
ASSERT_TRUE(h2.valid());
|
||||||
|
|
||||||
|
// Should point to same memory
|
||||||
|
int* data1 = static_cast<int*>(h1.get());
|
||||||
|
int* data2 = static_cast<int*>(h2.get());
|
||||||
|
|
||||||
|
*data1 = 12345;
|
||||||
|
EXPECT_EQ(*data2, 12345);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test large shared memory segment
|
||||||
|
TEST_F(ShmTest, LargeSegment) {
|
||||||
|
std::string name = generate_unique_name("large_segment");
|
||||||
|
const std::size_t size = 10 * 1024 * 1024; // 10 MB
|
||||||
|
|
||||||
|
shm::handle h(name.c_str(), size);
|
||||||
|
|
||||||
|
if (h.valid()) {
|
||||||
|
EXPECT_GE(h.size(), size);
|
||||||
|
|
||||||
|
// Write pattern to a portion of memory
|
||||||
|
char* mem = static_cast<char*>(h.get());
|
||||||
|
for (std::size_t i = 0; i < 1024; ++i) {
|
||||||
|
mem[i] = static_cast<char>(i % 256);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify pattern
|
||||||
|
for (std::size_t i = 0; i < 1024; ++i) {
|
||||||
|
EXPECT_EQ(mem[i], static_cast<char>(i % 256));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user