Compare commits

...

156 Commits

Author SHA1 Message Date
mutouyun
ce0773b3e6 refactor: improve name handling in shm_posix.cpp to match semaphore pattern
Check if name already starts with '/' before adding prefix, consistent
with the pattern used in semaphore_impl.h. This avoids duplicate prefix
when users provide names in the correct format.
2025-12-06 16:06:51 +08:00
木头云
addfe4f5cf
Merge pull request #161 from mutouyun/fix/freebsd-mutex-unlock-timing
Fix mutex unlock timing for FreeBSD multi-run stability
2025-12-06 15:22:27 +08:00
木头云
2593213d9c Fix FreeBSD shm_unlink failure by adding '/' prefix in remove()
Problem: Tests fail on the second run on FreeBSD with ShmTest.RemoveByName failing.
After the first test run completes, subsequent runs fail because shared memory
objects are not properly removed.

Root cause: FreeBSD's shm_unlink() is stricter than Linux about POSIX compliance.
The remove(char const * name) function was calling shm_unlink() without the '/'
prefix, while acquire() was using '/'+name format. This inconsistency caused:
- Linux: Silently tolerates both /name and name formats
- FreeBSD: Strictly requires /name format, shm_unlink("name") fails

When shm_unlink() fails to remove the shared memory object:
1. First test run creates /remove_by_name_test_1
2. Test calls shm::remove("remove_by_name_test_1")
3. shm_unlink("remove_by_name_test_1") fails on FreeBSD (missing '/')
4. Shared memory object remains in the system
5. Second test run tries to reuse the same name -> conflict -> test fails

Solution:
1. Fix remove(char const * name) to prepend '/' to the name for consistency
   with acquire() function, ensuring POSIX compliance
2. Add error checking for all shm_unlink() calls to log failures with errno

This ensures proper cleanup on FreeBSD and maintains compatibility with Linux.

Changes:
- Modified remove(char const * name) to use '/'+name format
- Added error logging for all three shm_unlink() calls
- Now consistent with POSIX requirement: shared memory names must be /somename

Tested on FreeBSD 15: Multiple consecutive test runs now pass without failures.
2025-12-06 07:20:33 +00:00
木头云
37f16cea47 Fix mutex unlock timing to avoid interfering with other references
Problem: The previous fix unconditionally called pthread_mutex_unlock() at the
beginning of close(), which could interfere with other threads/processes that
still had valid references to the mutex. This caused test failures on FreeBSD
when running tests multiple times (ShmTest.RemoveByName would fail on the second run).

Root cause: Calling unlock() too early could affect the mutex state for other
references that are still using it, leading to unexpected behavior.

Solution: Move pthread_mutex_unlock() to only be called when we're about to
destroy the mutex (i.e., when we're the last reference: shm_->ref() <= 1 &&
self_ref <= 1). This ensures:
1. We don't interfere with other threads/processes using the mutex
2. We still unlock before destroying to avoid FreeBSD robust list issues
3. The unlock happens at the correct time - right before pthread_mutex_destroy()

This is the correct approach because:
- Only the last reference holder should clean up the mutex
- Unlocking should be paired with destroying for the final cleanup
- Other references should not be affected by one reference closing

Fixes the second-run test failure on FreeBSD while maintaining the segfault fix.
2025-12-06 06:53:29 +00:00
木头云
0ba12144bd
Merge pull request #160 from mutouyun/issue/156
Fix FreeBSD test failures in POSIX implementation
2025-12-06 14:39:49 +08:00
木头云
5517f48681 Fix FreeBSD segfault by unlocking mutex before destruction
Root cause: FreeBSD's robust mutex implementation (libthr) maintains a per-thread
robust list of mutexes. The error 'rb error 14' (EFAULT - Bad address) indicates
that the robust list contained a dangling pointer to a destroyed mutex.

When a mutex object is destroyed (via close() or clear()), if the mutex is still
in the current thread's robust list, FreeBSD's libthr may try to access it later
and encounter an invalid pointer, causing a segmentation fault.

This happened in MutexTest.TryLockExceptionSafety because:
1. The test called try_lock() which successfully acquired the lock
2. The test ended without calling unlock()
3. The mutex destructor called close()
4. close() called pthread_mutex_destroy() on a mutex that was:
   - Still locked by the current thread, OR
   - Still in the thread's robust list

Solution:
Call pthread_mutex_unlock() before pthread_mutex_destroy() in both close()
and clear() methods. This ensures:
1. The mutex is unlocked if we hold the lock
2. The mutex is removed from the thread's robust list
3. Subsequent pthread_mutex_destroy() is safe

We ignore errors from pthread_mutex_unlock() because:
- If we don't hold the lock, EPERM is expected and harmless
- If the mutex is already unlocked, this is a no-op
- Even if there's an error, we still want to proceed with cleanup

This fix is platform-agnostic and should not affect Linux/QNX behavior,
as both also use pthread robust mutexes with similar semantics.

Fixes the segfault in MutexTest.TryLockExceptionSafety on FreeBSD 15.
2025-12-06 06:35:02 +00:00
木头云
47fa303455 Fix segfault in EOWNERDEAD handling - remove incorrect ref count manipulation
Root cause: The previous code incorrectly called shm_->sub_ref() when handling
EOWNERDEAD, which could cause the shared memory to be freed prematurely while
the mutex pointer was still in use, leading to segmentation fault.

Fix: Remove the shm_->sub_ref() call. When EOWNERDEAD is returned, it means
we have successfully acquired the lock. We only need to call pthread_mutex_consistent()
to make the mutex usable again, then return success. The shared memory reference
count should not be modified in this path.

This fixes the segfault in MutexTest.TryLockExceptionSafety on FreeBSD 15.
2025-12-06 06:21:38 +00:00
木头云
a82e3faf63 Fix C++17 compilation error on FreeBSD GCC 13.3
Remove custom deduction guides for std::unique_ptr in C++17 mode.

Issue: FreeBSD GCC 13.3 correctly rejects adding deduction guides
to namespace std, which violates C++ standard [namespace.std]:
"The behavior of a C++ program is undefined if it adds declarations
or definitions to namespace std or to a namespace within namespace std."

Root cause: The code attempted to add custom deduction guides for
std::unique_ptr in namespace std when compiling in C++17 mode.
This is not allowed by the C++ standard.

Solution: Remove the custom deduction guides for C++17 and later,
as the C++17 standard library already provides deduction guides
for std::unique_ptr (added in C++17 via P0433R2).

The custom deduction guide wrappers in the else branch (for C++14
and earlier) are kept as they provide helper functions, not actual
deduction guides in namespace std.

Tested-on: FreeBSD 15 with GCC 13.3
Fixes: Compilation error 'deduction guide must be declared in the
same scope as template std::unique_ptr'
2025-12-06 06:13:58 +00:00
木头云
543672b39d Fix FreeBSD-specific issues in POSIX implementation
This commit addresses the test failures reported in issue #156
on FreeBSD platform.

1. Fix POSIX semaphore naming for FreeBSD compatibility
   - FreeBSD requires semaphore names to start with "/"
   - Add "_sem" suffix to avoid namespace conflicts with shm
   - Store semaphore name separately for proper cleanup
   - This fixes all 24 semaphore test failures

2. Fix robust mutex EOWNERDEAD handling
   - When pthread_mutex_lock returns EOWNERDEAD, the lock is
     already acquired by the calling thread
   - After calling pthread_mutex_consistent(), we should return
     success immediately, not unlock and retry
   - Previous behavior caused issues with FreeBSD's libthr robust
     mutex list management, leading to fatal errors
   - This fixes the random crashes in MutexTest

Technical details:
- EOWNERDEAD indicates the previous lock owner died while holding
  the lock, but the current thread has successfully acquired it
- pthread_mutex_consistent() restores the mutex to a consistent state
- The Linux implementation worked differently, but the new approach
  is more correct according to POSIX semantics and works on both
  Linux and FreeBSD

Fixes #156
2025-12-06 06:05:53 +00:00
木头云
006a46e09f
Merge pull request #158 from mutouyun/feature/issue-156
Add FreeBSD platform support (fixes #156)
2025-12-03 16:49:36 +08:00
木头云
f8e71a548c
Merge pull request #159 from mutouyun/refactoring-ut
refactor(test): comprehensive unit test refactoring
2025-11-30 19:36:52 +08:00
木头云
cf5738eb3a fix(test): replace C++17 structured bindings with C++14 compatible code
Problem:
- Two test cases in test_buffer.cpp used structured bindings (auto [a, b])
- Structured bindings are a C++17 feature
- Project requires C++14 compatibility

Solution:
- Replace 'auto [ptr, size] = buf.to_tuple()' with C++14 compatible code
- Use std::get<N>() to extract tuple elements
- Modified tests: ToTupleNonConst, ToTupleConst

Changes:
- Line 239: Use std::get<0/1>(tuple) instead of structured binding
- Line 252: Use std::get<0/1>(tuple) instead of structured binding
- Add explanatory comments for clarity

This ensures the test suite compiles with C++14 standard.
2025-11-30 11:16:03 +00:00
木头云
c31ef988c1 fix(shm): remove redundant self-assignment in shm_win.cpp
- Remove useless 'ii->size_ = ii->size_;' statement at line 140
- The user-requested size is already set in acquire() function
- Simplify else branch to just a comment for clarity
- No functional change, just code cleanup
2025-11-30 11:06:04 +00:00
木头云
7726742157 feat(shm): implement reference counting for Windows shared memory
Problem:
- Reference counting tests fail on Windows (ReleaseMemory, ReferenceCount,
  SubtractReference, HandleRef, HandleSubRef)
- get_ref() and sub_ref() were stub implementations returning 0/doing nothing
- CreateFileMapping HANDLE lacks built-in reference counting mechanism

Solution:
- Implement reference counting using std::atomic<std::int32_t> stored at
  the end of shared memory (same strategy as POSIX version)
- Add calc_size() helper to allocate extra space for atomic counter
- Add acc_of() helper to access the atomic counter at the end of memory
- Modify acquire() to allocate calc_size(size) instead of size
- Modify get_mem() to initialize counter to 1 on first mapping
- Modify release() to decrement counter and return ref count before decrement
- Implement get_ref() to return current reference count
- Implement sub_ref() to atomically decrement reference count
- Convert file from Windows (CRLF) to Unix (LF) line endings for consistency

Key Implementation Details:
1. Reference counter stored at end of shared memory (aligned to info_t)
2. First get_mem() call: fetch_add(1) initializes counter to 1
3. release() returns ref count before decrement (for semantics compatibility)
4. Memory layout: [user data][padding][atomic<int32_t> counter]
5. Uses memory_order_acquire/release/acq_rel for proper synchronization

This makes Windows implementation match POSIX behavior and ensures all
reference counting tests pass on Windows platform.
2025-11-30 10:55:24 +00:00
木头云
b9dd75ccd9 fix(platform): rename linux/posix namespaces to avoid predefined macro conflict
Problem:
- 'linux' is a predefined macro on Linux platforms
- Using 'namespace linux' causes compilation errors
- Preprocessor replaces 'linux' with '1' before compilation

Solution:
- Rename 'namespace linux' to 'namespace linux_'
- Rename 'namespace posix' to 'namespace posix_'
- Update all 7 call sites accordingly:
  - linux/condition.h:  linux_::detail::make_timespec()
  - linux/mutex.h:      linux_::detail::make_timespec() (2 places)
  - posix/condition.h:  posix_::detail::make_timespec()
  - posix/mutex.h:      posix_::detail::make_timespec() (2 places)
  - posix/semaphore_impl.h: posix_::detail::make_timespec()

This prevents preprocessor macro expansion issues while maintaining
the ODR violation fix from the previous commit.
2025-11-30 07:07:14 +00:00
木头云
e66bd880e9 fix(platform): resolve ODR violation in make_timespec/calc_wait_time inline functions
Problem:
- Both linux/get_wait_time.h and posix/get_wait_time.h define inline functions
  make_timespec() and calc_wait_time() in namespace ipc::detail
- On Linux, semaphore uses posix implementation, but may include both headers
- This causes ODR (One Definition Rule) violation - undefined behavior
- Different inline function definitions with same name violates C++ standard
- Manifested as test failures in SemaphoreTest::WaitTimeout

Solution:
- Add platform-specific namespace layer between ipc and detail:
  - linux/get_wait_time.h: ipc::linux::detail::make_timespec()
  - posix/get_wait_time.h: ipc::posix::detail::make_timespec()
- Update all call sites to use fully qualified names:
  - linux/condition.h: linux::detail::make_timespec()
  - linux/mutex.h: linux::detail::make_timespec() (2 places)
  - posix/condition.h: posix::detail::make_timespec()
  - posix/mutex.h: posix::detail::make_timespec() (2 places)
  - posix/semaphore_impl.h: posix::detail::make_timespec()

This ensures each platform's implementation is uniquely named, preventing
ODR violations and ensuring correct function resolution at compile time.
2025-11-30 07:00:32 +00:00
木头云
ff74cdd57a fix(test): correct receiver loop count in MultipleSendersReceivers
Problem:
- Receivers were exiting after receiving only messages_per_sender (5) messages
- In broadcast mode, each message is sent to ALL receivers
- If sender1 completes quickly, all receivers get 5 messages and exit
- This causes sender2's messages to fail (no active receivers)

Solution:
- Each receiver should loop for num_senders * messages_per_sender (2 * 5 = 10) messages
- This ensures all receivers stay active until ALL senders complete
- Now receivers wait for: 2 senders × 5 messages each = 10 total messages
- Expected received_count: 2 senders × 5 messages × 2 receivers = 20 messages

This fix ensures all senders can successfully complete their sends before
any receiver exits.
2025-11-30 06:38:38 +00:00
木头云
78be284668 fix(test): correct test logic and semantics in multiple test cases
1. ChannelTest::MultipleSendersReceivers
   - Add C++14-compatible latch implementation (similar to C++20 std::latch)
   - Ensure receivers are ready before senders start sending messages
   - This prevents race condition where senders might send before receivers are listening

2. RWLockTest::ReadWriteReadPattern
   - Fix test logic: lock_shared allows multiple concurrent readers
   - Previous test had race condition where both threads could read same value
   - New test: each thread writes based on thread id (1 or 2), then reads
   - Expected result: 1*20 + 2*20 = 60

3. ShmTest::ReleaseMemory
   - Correct return value semantics: release() returns ref count before decrement, or -1 on error
   - Must call get_mem() to map memory and set ref count to 1 before release
   - Expected: release() returns 1 (ref count before decrement)

4. ShmTest::ReferenceCount
   - Correct semantics: ref count is 0 after acquire (memory not mapped)
   - get_mem() maps memory and sets ref count to 1
   - Second acquire+get_mem increases ref count to 2
   - Test now properly validates reference counting behavior

5. ShmTest::SubtractReference
   - sub_ref() only works after get_mem() has mapped the memory
   - Must call get_mem() first to initialize ref count to 1
   - sub_ref() then decrements it to 0
   - Test now follows correct API usage pattern
2025-11-30 06:06:54 +00:00
木头云
d5f787596a fix(test): fix double-free in HandleDetachAttach test
- Problem: calling h2.release() followed by shm::remove(id) causes use-after-free
  - h2.release() internally calls shm::release(id) which frees the id structure
  - shm::remove(id) then accesses the freed id pointer -> crash

- Solution: detach the id from handle first, then call shm::remove(id)
  - h2.detach() returns the id without releasing it
  - shm::remove(id) can then safely clean up both memory and disk file

- This completes the fix for all ShmTest double-free issues
2025-11-30 05:38:59 +00:00
木头云
0ecf1a4137 docs(shm): add semantic comments for release/remove and fix double-free in tests
- Add comprehensive documentation for shm::release(id), shm::remove(id), and shm::remove(name)
  - release(id): Decrements ref count, cleans up memory and disk file when count reaches zero
  - remove(id): Calls release(id) internally, then forces disk file cleanup (WARNING: do not use after release)
  - remove(name): Only removes disk file, safe to use anytime

- Fix critical double-free bug in ShmTest test cases
  - Problem: calling release(id) followed by remove(id) causes use-after-free crash
    because release() already frees the id structure
  - Solution: replace 'release(id); remove(id);' pattern with just 'remove(id)'
  - Fixed tests: AcquireCreate, AcquireCreateOrOpen, GetMemory, GetMemoryNoSize,
    RemoveById, SubtractReference
  - Kept 'release(id); remove(name);' pattern unchanged (safe usage)

- Add explanatory comments in test code to prevent future misuse
2025-11-30 05:28:48 +00:00
木头云
91e4489a55 refactor(buffer): rename 'additional' parameter to 'mem_to_free' for clarity
Header changes (include/libipc/buffer.h):
- Rename: additional → mem_to_free (better semantic name)
- Add documentation comments explaining the parameter's purpose
- Clarifies that mem_to_free is passed to destructor instead of p
- Use case: when data pointer is offset into a larger allocation

Implementation changes (src/libipc/buffer.cpp):
- Update parameter name in constructor implementation
- No logic changes, just naming improvement

Test changes (test/test_buffer.cpp):
- Fix TEST_F(BufferTest, ConstructorWithMemToFree)
- Previous test caused crash: passed stack variable address to destructor
- New test correctly demonstrates the parameter's purpose:
  * Allocate 100-byte block
  * Use offset portion (bytes 25-75) as data
  * Destructor receives original block pointer for proper cleanup
- Prevents double-free and invalid free errors

Semantic explanation:
  buffer(data_ptr, size, destructor, mem_to_free)

  On destruction:
    destructor(mem_to_free ? mem_to_free : data_ptr, size)

  This allows:
    char* block = new char[100];
    char* data = block + 25;
    buffer buf(data, 50, my_free, block);  // Frees 'block', not 'data'
2025-11-30 05:09:56 +00:00
木头云
de76cf80d5 fix(buffer): remove const from char constructor to prevent UB
- Change: explicit buffer(char const & c) → explicit buffer(char & c)
- Remove dangerous const_cast in implementation
- Before: buffer(const_cast<char*>(&c), 1) [UB if c is truly const]
- After: buffer(&c, 1) [safe, requires non-const char]
- Prevents undefined behavior from modifying compile-time constants
- Constructor now correctly requires mutable char reference
- Aligns with buffer's mutable data semantics

The previous implementation with const_cast could lead to:
- Modifying string literals (undefined behavior)
- Modifying const variables (undefined behavior)
- Runtime crashes or data corruption

Example of prevented misuse:
  buffer buf('X');           // Now: compile error ✓
  char c = 'X';
  buffer buf(c);             // Now: works correctly ✓
  const char cc = 'Y';
  buffer buf(cc);            // Now: compile error ✓
2025-11-30 05:00:57 +00:00
木头云
8103c117f1 fix(buffer): remove redundant const qualifier in array constructor
- Change: byte_t const (& data)[N] → byte_t (& data)[N]
- Allows non-const byte arrays to be accepted by the constructor
- Fixes defect discovered by TEST_F(BufferTest, ConstructorFromByteArray)
- The const qualifier on array elements was too restrictive
- Keep char const & c unchanged as it's correct for single char reference
2025-11-30 04:56:02 +00:00
木头云
7447a86d41 fix(test): correct member vs non-member function calls in test_shm.cpp
- Fix handle class member functions: remove incorrect shm:: prefix
  - h.acquire() not h.shm::acquire()
  - h.release() not h.shm::release()
  - h.sub_ref() not h.shm::sub_ref()
- Keep shm:: prefix for namespace-level global functions:
  - shm::acquire(), shm::release(id), shm::get_mem()
  - shm::remove(), shm::get_ref(id), shm::sub_ref(id)
- Fix comments to use correct terminology
- Resolves: 'shm::acquire is not a class member' compilation errors
2025-11-30 04:50:21 +00:00
木头云
7092df53bb fix(test): resolve id_t ambiguity in test_shm.cpp
- Remove 'using namespace ipc::shm;' to avoid id_t conflict with system id_t
- Add explicit shm:: namespace prefix to all shm types and functions
- Apply to: id_t, handle, acquire, get_mem, release, remove, get_ref, sub_ref
- Apply to: create and open constants
- Fix comments to avoid incorrect namespace references
- Resolves compilation error: 'reference to id_t is ambiguous'
2025-11-30 04:29:55 +00:00
木头云
2cde78d692 style(test): change indentation from 4 spaces to 2 spaces
- Update all test files to use 2-space indentation
- Affects: test_buffer.cpp, test_shm.cpp, test_mutex.cpp
- Affects: test_semaphore.cpp, test_condition.cpp
- Affects: test_locks.cpp, test_ipc_channel.cpp
- Improves code consistency and readability
2025-11-30 04:22:24 +00:00
木头云
b5146655fa build(test): update CMakeLists.txt for new test structure
- Collect only test_*.cpp files from test directory
- Exclude archive directory from compilation
- Use glob pattern to automatically include new tests
- Maintain same build configuration and dependencies
- Link with gtest, gtest_main, and ipc library
2025-11-30 04:16:41 +00:00
木头云
9070a899ef test(ipc): add comprehensive unit tests for route and channel
- Test route (single producer, multiple consumer) functionality
- Test channel (multiple producer, multiple consumer) functionality
- Test construction with name and prefix
- Test connection, disconnection, and reconnection
- Test send/receive with buffer, string, and raw data
- Test blocking send/recv and non-blocking try_send/try_recv
- Test timeout handling
- Test one-to-many broadcast (route)
- Test many-to-many communication (channel)
- Test recv_count and wait_for_recv functionality
- Test clone, release, and clear operations
- Test resource cleanup and storage management
- Test concurrent multi-sender and multi-receiver scenarios
2025-11-30 04:16:16 +00:00
木头云
c21138b5b4 test(locks): add comprehensive unit tests for spin_lock and rw_lock
- Test spin_lock basic operations and mutual exclusion
- Test spin_lock critical section protection
- Test spin_lock concurrent access and contention
- Test rw_lock write lock (exclusive access)
- Test rw_lock read lock (shared access)
- Test multiple concurrent readers
- Test writers have exclusive access
- Test readers and writers don't overlap
- Test various read-write patterns
- Test rapid lock/unlock operations
- Test mixed concurrent operations
- Test write lock blocks readers correctly
2025-11-30 04:14:52 +00:00
木头云
4832c47345 test(condition): add comprehensive unit tests for ipc::sync::condition
- Test condition variable construction (default and named)
- Test wait, notify, and broadcast operations
- Test timed wait with timeout and infinite wait
- Test integration with mutex for synchronization
- Test producer-consumer patterns with condition variables
- Test multiple waiters with broadcast
- Test spurious wakeup handling patterns
- Test named condition variable sharing between threads
- Test resource cleanup (clear, clear_storage)
- Test edge cases (after clear, immediate notify)
2025-11-30 04:13:57 +00:00
木头云
6e17ce184b test(semaphore): add comprehensive unit tests for ipc::sync::semaphore
- Test semaphore construction (default and named with count)
- Test wait and post operations
- Test timed wait with various timeout values
- Test producer-consumer patterns
- Test multiple producers and consumers scenarios
- Test concurrent post operations
- Test initial count behavior
- Test named semaphore sharing between threads
- Test resource cleanup (clear, clear_storage)
- Test edge cases (zero timeout, after clear, high frequency)
2025-11-30 04:13:04 +00:00
木头云
b4ad3c69c9 test(mutex): add comprehensive unit tests for ipc::sync::mutex
- Test mutex construction (default and named)
- Test lock/unlock operations
- Test try_lock functionality
- Test timed lock with various timeout values
- Test critical section protection
- Test concurrent access and contention scenarios
- Test inter-thread synchronization with named mutex
- Test resource cleanup (clear, clear_storage)
- Test native handle access
- Test edge cases (reopen, zero timeout, rapid operations)
- Test exception safety of try_lock
2025-11-30 04:12:14 +00:00
木头云
280a21c89a test(shm): add comprehensive unit tests for shared memory
- Test low-level API (acquire, get_mem, release, remove)
- Test reference counting functionality (get_ref, sub_ref)
- Test high-level handle class interface
- Test all handle methods (valid, size, name, get, etc.)
- Test handle lifecycle (construction, move, swap, assignment)
- Test different access modes (create, open, create|open)
- Test detach/attach functionality
- Test multi-handle access to same memory
- Test data persistence across handles
- Test edge cases (large segments, multiple simultaneous handles)
2025-11-30 04:11:13 +00:00
木头云
3d743d57ac test(buffer): add comprehensive unit tests for ipc::buffer
- Test all constructors (default, with destructor, from array, from char)
- Test move semantics and assignment operators
- Test all accessor methods (data, size, empty, get<T>)
- Test conversion methods (to_tuple, to_vector)
- Test comparison operators (==, !=)
- Test edge cases (empty buffers, large buffers, self-assignment)
- Verify destructor callback functionality
2025-11-30 04:10:07 +00:00
木头云
17eaa573ca chore(test): archive existing test cases to test/archive
- Move all existing test files (*.cpp, *.h) to test/archive/
- Rename CMakeLists.txt to CMakeLists.txt.old in archive
- Prepare for comprehensive unit test refactoring
2025-11-30 04:04:10 +00:00
木头云
0d53a3cdb1 Add FreeBSD platform support (fixes #156)
- Add IPC_OS_FREEBSD_ platform detection macro
- Enable FreeBSD to use POSIX pthread implementation (shared with QNX)
- Update all conditional compilation directives to include FreeBSD
- Update README to reflect FreeBSD platform support

FreeBSD uses the existing POSIX implementation which supports:
- Process-shared mutexes (PTHREAD_PROCESS_SHARED)
- Robust mutexes (PTHREAD_MUTEX_ROBUST)
- Timed lock operations
- POSIX shared memory

This is a minimal change that reuses the mature POSIX implementation
already proven by QNX platform support.
2025-11-29 10:55:54 +00:00
木头云
c5302d00ab
Merge pull request #157 from cscd98/mingw
mingw: use lower case windows.h
2025-11-29 18:25:36 +08:00
Craig Carnell
72c4b5abc4 mingw: use lower case windows.h 2025-11-17 09:56:49 +00:00
木头云
a0c7725a14
Merge pull request #148 from mutouyun/yonker-yk-master
Yonker yk master
2025-05-11 21:40:37 +08:00
mutouyun
a1cdc9a711 In non-broadcast mode, connection tags are only used for counting. 2025-05-10 15:14:39 +08:00
yongke liu
87b1fa4abc Fixed issue 107 and 123, receiver check connection when pop msg failed, and call reconnect function when the connection check result is false 2025-05-09 17:10:07 +08:00
木头云
120d85a2c4
Merge pull request #145 from johnwongx/sync
修复连接槽满判断错误
2025-05-01 11:13:43 +08:00
johnwongx
a6c7c8542f 修复连接槽满判断错误 2025-04-23 13:15:39 +08:00
木头云
fdcc9340be
Update rw_lock.h for #143 2025-04-20 13:58:42 +08:00
木头云
f3bf137668
Merge pull request #139 from aengusjiang/master
acquire 仅open不存在的shm不应该打印错误日志
2025-03-08 15:52:35 +08:00
Aengus.Jiang
7bb5f2e611 Merge branch 'master' of https://github.com/aengusjiang/cpp-ipc 2025-03-07 12:34:38 +08:00
Aengus.Jiang
06d4aec320 posix shm open 失败时如果文件不存在打印log #2 修改逻辑错误 2025-03-07 12:33:42 +08:00
Aengus.Jiang
5c36b1264f posix shm open 失败时如果文件不存在打印log 2025-03-07 11:57:31 +08:00
Aengus.Jiang
d69093462a open的时候不存在共享内存,则返回false,没有必要报错 2025-03-07 11:36:57 +08:00
木头云
df09c22738
Update README.md 2025-02-09 17:00:48 +08:00
mutouyun
2673453e66 Try to fix permission issues under linux 2024-12-01 21:06:06 +08:00
mutouyun
84bb801b6e Try to fix a communication problem caused by different permissions under linux 2024-12-01 19:53:40 +08:00
mutouyun
5e5b347636 Complete the implementation of the clean interface and add unit tests 2024-12-01 19:06:50 +08:00
mutouyun
28fdf17279 Added cleanup interfaces for ipc chan 2024-12-01 17:49:34 +08:00
mutouyun
17dcde92bf Added clear_storage for quieue 2024-12-01 17:49:34 +08:00
mutouyun
ab90437e44 Added a cleanup interface for waiter. 2024-12-01 17:49:34 +08:00
mutouyun
acea9d74da Fix ut 2024-11-17 17:51:18 +08:00
mutouyun
e1f377d7f6 Added a cleanup interface for the synchronization facilities 2024-11-17 17:39:03 +08:00
mutouyun
29678f1d41 Added a cleanup interface for queue 2024-11-17 17:36:09 +08:00
mutouyun
5071fb5db6 Added a cleanup interface for shared memory handles 2024-11-17 17:35:29 +08:00
mutouyun
805490605e refactor: improve emplace construction for shm_data in mutex.h 2024-05-25 17:33:33 +08:00
abathur puppe
025311d5f6 fix emplace construction for shm_data. Previous required copy constructor 2024-05-25 17:32:31 +08:00
winsoft666
035d76d5aa Update CMakeLists.txt 2023-12-10 21:07:18 +08:00
winsoft666
144b2db9ca Add PACKAGE_VERSION 2023-12-10 21:07:18 +08:00
winsoft666
c4280efd5f Add cpp-ipc-targets 2023-12-10 21:07:18 +08:00
mutouyun
ac54be7083 reconnect cannot reconnect when you are out of authority 2023-10-28 16:44:16 +08:00
mutouyun
a3b0a968f8 回滚多余的修改 2023-10-28 16:44:16 +08:00
mutouyun
fafa5e85f7 Fixed memory access exception in multithreading. 2023-10-28 16:44:16 +08:00
mutouyun
c74f78ea08 统一字符串有效性判断 2023-10-28 16:44:16 +08:00
mutouyun
cf72d0293a The global sender could not be obtained due to different prefixes. 2023-10-28 16:44:16 +08:00
mutouyun
fab3f6fffe Add a user interface with a custom name prefix. 2023-10-28 16:44:16 +08:00
mutouyun
16b138d6af Check illegal parameter. 2023-10-28 16:44:16 +08:00
mutouyun
a46773bbd5 微调注释 2023-10-28 16:44:16 +08:00
mutouyun
ec14e81ffd Identify the user group and add the appropriate prefix to the names. 2023-10-28 16:44:16 +08:00
mutouyun
bbd063f965 调整空白格式 2023-10-28 16:44:16 +08:00
mutouyun
0814438c35 调整注释 2023-10-28 16:44:16 +08:00
mutouyun
22a253a72f Supplement similar demo under linux. 2023-10-28 16:44:16 +08:00
mutouyun
e229f78a15 Windows services can communicate with common processes. 2023-10-28 16:44:16 +08:00
mutouyun
7981a1cbc1 disable warning C4858 2023-10-28 16:44:16 +08:00
木头云
6111722534
fix: the receiver of channel will hang after disconnect 2023-05-14 14:11:54 +08:00
mutouyun
2a2b626210 补充遗漏的初始化 2023-02-25 16:30:11 +08:00
mutouyun
162011d4b4 修正全局变量初始化时序问题导致的内存访问异常 2023-02-25 16:30:11 +08:00
木头云
768e58f605
Merge pull request #82 from SuperWangKai/master
Added English translation for README.md.
2022-08-04 22:12:26 +08:00
Kai Wang
1fca15f741 Placed a space before parenthesis. 2022-08-03 22:03:42 +08:00
Kai Wang
97dd413725 Added back two spaces to keep new lines. 2022-08-03 21:57:10 +08:00
Kai Wang
65043dec54 Minor changes. 2022-08-01 13:55:04 +08:00
Kai Wang
dea332364a Added English translation for README.md. 2022-08-01 13:34:16 +08:00
木头云
f2f1af8f8e
Merge pull request #74 from mutouyun/develop
Develop
2022-01-08 23:53:33 +08:00
木头云
f25668c4c2
Update CMakeLists.txt 2022-01-08 23:23:45 +08:00
木头云
5eebaeb4ee
Update mutex.h 2022-01-08 23:18:30 +08:00
木头云
15b572b7ed
Update condition.h 2022-01-08 23:18:05 +08:00
mutouyun
de9c965046 compile error for qnx 2022-01-07 22:52:34 +08:00
mutouyun
534870b824 qnx 2022-01-05 09:37:36 +08:00
mutouyun
1f65fc9832 compile error 2022-01-02 17:56:05 +08:00
mutouyun
2e35ab7685 Added QNX support 2022-01-02 17:54:07 +08:00
mutouyun
51828c2f7b Temporarily turn off 'smu' and 'mmu' modes because there are bugs in them 2022-01-02 17:24:08 +08:00
mutouyun
3344bbf799 impl robust mutex & condition (using alephzero's mtx implementation) 2021-10-23 19:06:33 +08:00
mutouyun
96551d5fcb fix compilation errors caused by paths 2021-10-23 17:27:55 +08:00
mutouyun
d946ad0948 modify interface of sync.condition 2021-10-23 17:27:08 +08:00
mutouyun
4ddc1d0a3d adjust directory paths 2021-10-23 17:18:23 +08:00
木头云
a5722957b2
Merge pull request #69 from mutouyun/develop
Develop
2021-10-20 22:49:18 +08:00
mutouyun
7d878d7f5a performance.xlsx 更新图示说明 2021-10-17 14:32:38 +08:00
mutouyun
1e092bb298 Merge branch 'master' into develop 2021-10-17 14:19:07 +08:00
木头云
74e871e6e4
warning: enumeral and non-enumeral type in conditional expression 2021-10-14 22:37:22 +08:00
木头云
1c9569a90f
Merge pull request #64 from winsoft666/master
支持Vcpkg方式安装
2021-09-25 15:00:55 +08:00
winsoft666
71a6efa3ca 支持Vcpkg方式安装 2021-09-24 12:40:11 +08:00
木头云
fbf1c89f70
Merge pull request #63 from mutouyun/develop
Develop
2021-09-21 14:07:43 +08:00
mutouyun
a457a8975f using 'signal' to quit waiting explicitly 2021-09-21 13:09:59 +08:00
mutouyun
f6bd578c8a reduce the number of recheck times for the sleep function 2021-09-20 23:29:30 +08:00
mutouyun
7a536b6e9c impl quit_waiting 2021-09-20 22:18:27 +08:00
mutouyun
b8f5e2ba6f validate close in waiter 2021-09-20 22:03:36 +08:00
mutouyun
ed8b1fd608 fix some bugs for linux-mutex 2021-09-20 20:31:08 +08:00
mutouyun
a9cb81bee9 missing file 2021-09-20 16:05:35 +08:00
mutouyun
04fda1cc3d use sync to refactor waiter 2021-09-20 15:59:44 +08:00
mutouyun
c1ceaa657a 实现condition_win 2021-09-19 22:26:32 +08:00
mutouyun
0cccdac868 merge issue-61; add condition for linux 2021-09-19 17:21:39 +08:00
mutouyun
4ca300b3e5 Merge branch 'issue-61' into develop 2021-09-19 16:29:31 +08:00
mutouyun
be6f16f87f revert some changes 2021-09-19 16:29:06 +08:00
mutouyun
68590dd2f3 commit new demo 2021-09-19 16:14:48 +08:00
mutouyun
94ad05ce35 调整ut 2021-09-18 00:11:11 +08:00
mutouyun
843770442c 避免wait_if的counter因为ABA问题导致计数错误 2021-09-17 22:25:53 +08:00
mutouyun
91385d727a 修正recv中断后counter无法下降的问题;添加新的示例 2021-09-17 22:01:34 +08:00
mutouyun
baf645eea1 修正 recv timeout 接口cpu占用过高的问题 2021-09-16 23:49:01 +08:00
mutouyun
ca9c5d10da ut for sem-linux 2021-09-12 22:05:08 +08:00
mutouyun
1994243bec fix: semaphore() noexcept is implicitly deleted 2021-09-12 21:58:57 +08:00
木头云
cd4b28380c
Update semaphore_linux.h 2021-09-12 21:51:08 +08:00
mutouyun
d37a6740ea add ut for sync::semaphore 2021-09-12 21:48:22 +08:00
mutouyun
d0e2a4d80c add semaphore for win 2021-09-12 15:59:44 +08:00
mutouyun
415be36477 ipc::sync::mutex for linux 2021-09-11 15:52:48 +08:00
mutouyun
1dc0419865 ignore invalid id print in get_ref 2021-08-29 13:58:34 +08:00
木头云
754661c467
Merge pull request #59 from mutouyun/master 2021-08-29 11:05:54 +08:00
木头云
78be14be37
Merge branch 'develop' into master 2021-08-29 11:05:24 +08:00
木头云
d80bea9b5d
fix: unexpected crash
An unexpected crash caused by an unconnected exit.
2021-08-23 13:10:03 +08:00
mutouyun
d74f4c5609 fix: mutex() noexcept is implicitly deleted 2021-06-21 00:06:17 +08:00
mutouyun
dd29ed5d1f fix errors 2021-06-21 00:02:48 +08:00
mutouyun
40eafcfd2a fix errors 2021-06-21 00:00:11 +08:00
mutouyun
2d0948c042 Merge branch 'develop' of github.com:mutouyun/cpp-ipc into develop 2021-06-20 23:52:40 +08:00
mutouyun
0e0bbd729c remove .travis.yml 2021-06-20 23:52:22 +08:00
mutouyun
a970ace446 using IPC_OS_* in test 2021-06-20 23:50:39 +08:00
mutouyun
d974641a07 for vs2015 error C3256 2021-06-20 23:50:39 +08:00
mutouyun
455c0b479d add sync::mutex for windows/linux 2021-06-20 23:50:39 +08:00
mutouyun
12944502a1 Revert "update IPC_CONCEPT_"
This reverts commit 1e5547e6dfd0605fa62be67899c6c893aa61f9fc.
2021-06-20 23:50:39 +08:00
mutouyun
85342dcaa6 test/profiler 2021-06-20 23:50:39 +08:00
mutouyun
563aabfe4b add profiler from adah1972 2021-06-20 23:50:39 +08:00
mutouyun
8cd2a40bfd update IPC_CONCEPT_ 2021-06-20 23:50:39 +08:00
mutouyun
3cf7d5bcd5 using IPC_OS_* in test 2021-06-20 15:49:38 +08:00
mutouyun
e5bf3b7c84 for vs2015 error C3256 2021-06-20 15:32:54 +08:00
mutouyun
e20dd7d5e3 add sync::mutex for windows/linux 2021-06-20 15:23:44 +08:00
mutouyun
ff488e002f Revert "update IPC_CONCEPT_"
This reverts commit 1e5547e6dfd0605fa62be67899c6c893aa61f9fc.
2021-06-06 19:40:54 +08:00
mutouyun
54bc3386dd test/profiler 2021-06-06 19:35:39 +08:00
mutouyun
55e75d4ed6 add profiler from adah1972 2021-06-06 19:34:18 +08:00
mutouyun
c04e726c23 Merge branch 'develop' of https://github.com/mutouyun/cpp-ipc into develop 2021-06-06 19:08:04 +08:00
mutouyun
1e5547e6df update IPC_CONCEPT_ 2021-06-06 19:05:14 +08:00
木头云
dbd31e1226
Merge pull request #29 from mutouyun/master
Merge from master
2021-01-11 11:05:46 +08:00
木头云
321dc458fd
Merge pull request #26 from mutouyun/master
update develop from master
2021-01-03 15:15:32 +08:00
mutouyun
99f281c4c9 update IPC_CONCEPT_ 2020-11-28 22:26:07 +08:00
109 changed files with 9497 additions and 2514 deletions

1
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

72
demo/send_recv/main.cpp Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,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();

View File

@ -0,0 +1,42 @@
#pragma once
#include <cstdint> // std::uint64_t
#include "libipc/export.h"
#include "libipc/def.h"
#include "libipc/mutex.h"
namespace ipc {
namespace sync {
class IPC_EXPORT condition {
condition(condition const &) = delete;
condition &operator=(condition const &) = delete;
public:
condition();
explicit condition(char const *name);
~condition();
void const *native() const noexcept;
void *native() noexcept;
bool valid() const noexcept;
bool open(char const *name) noexcept;
void close() noexcept;
void clear() noexcept;
static void clear_storage(char const * name) noexcept;
bool wait(ipc::sync::mutex &mtx, std::uint64_t tm = ipc::invalid_value) noexcept;
bool notify(ipc::sync::mutex &mtx) noexcept;
bool broadcast(ipc::sync::mutex &mtx) noexcept;
private:
class condition_;
condition_* p_;
};
} // namespace sync
} // namespace ipc

View File

@ -25,13 +25,16 @@ using uint_t = typename uint<N>::type;
// constants // constants
enum : std::uint32_t {
invalid_value = (std::numeric_limits<std::uint32_t>::max)(),
default_timeout = 100, // ms
};
enum : std::size_t { enum : std::size_t {
invalid_value = (std::numeric_limits<std::size_t>::max)(),
data_length = 64, data_length = 64,
large_msg_limit = data_length, large_msg_limit = data_length,
large_msg_align = 1024, large_msg_align = 1024,
large_msg_cache = 32, large_msg_cache = 32,
default_timeout = 100 // ms
}; };
enum class relat { // multiplicity of the relationship enum class relat { // multiplicity of the relationship
@ -62,4 +65,9 @@ struct relat_trait<wr<Rp, Rc, Ts>> {
template <template <typename> class Policy, typename Flag> template <template <typename> class Policy, typename Flag>
struct relat_trait<Policy<Flag>> : relat_trait<Flag> {}; struct relat_trait<Policy<Flag>> : relat_trait<Flag> {};
// the prefix tag of a channel
struct prefix {
char const *str;
};
} // namespace ipc } // namespace ipc

View File

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

42
include/libipc/mutex.h Normal file
View File

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

View File

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

View File

@ -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.
@ -72,7 +73,7 @@ inline void yield(K& k) noexcept {
++k; ++k;
} }
template <std::size_t N = 4096, typename K, typename F> template <std::size_t N = 32, typename K, typename F>
inline void sleep(K& k, F&& f) { inline void sleep(K& k, F&& f) {
if (k < static_cast<K>(N)) { if (k < static_cast<K>(N)) {
std::this_thread::yield(); std::this_thread::yield();
@ -84,7 +85,7 @@ inline void sleep(K& k, F&& f) {
++k; ++k;
} }
template <std::size_t N = 4096, typename K> template <std::size_t N = 32, typename K>
inline void sleep(K& k) { inline void sleep(K& k) {
sleep<N>(k, [] { sleep<N>(k, [] {
std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(1));
@ -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:

View File

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

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <cstddef> #include <cstddef>
#include <cstdint>
#include "libipc/export.h" #include "libipc/export.h"
@ -14,11 +15,36 @@ enum : unsigned {
open = 0x02 open = 0x02
}; };
IPC_EXPORT id_t acquire(char const * name, std::size_t size, unsigned mode = create | open); IPC_EXPORT id_t acquire(char const * name, std::size_t size, unsigned mode = create | open);
IPC_EXPORT void * get_mem(id_t id, std::size_t * size); IPC_EXPORT void * get_mem(id_t id, std::size_t * size);
IPC_EXPORT void release(id_t id);
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 void sub_ref(id_t id);
class IPC_EXPORT handle { class IPC_EXPORT handle {
public: public:
@ -31,12 +57,19 @@ public:
void swap(handle& rhs); void swap(handle& rhs);
handle& operator=(handle rhs); handle& operator=(handle rhs);
bool valid() const; bool valid() const noexcept;
std::size_t size () const; std::size_t size () const noexcept;
char const * name () const; char const * name () const noexcept;
std::int32_t ref() const noexcept;
void sub_ref() noexcept;
bool acquire(char const * name, std::size_t size, unsigned mode = create | open); bool acquire(char const * name, std::size_t size, unsigned mode = create | open);
void 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;

View File

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

Binary file not shown.

View File

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

View File

@ -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)

View File

@ -37,8 +37,8 @@ private:
elem_t block_[elem_max] {}; elem_t block_[elem_max] {};
/** /**
* @remarks 'warning C4348: redefinition of default parameter' with MSVC. * \remarks 'warning C4348: redefinition of default parameter' with MSVC.
* @see * \see
* - https://stackoverflow.com/questions/12656239/redefinition-of-default-template-parameter * - https://stackoverflow.com/questions/12656239/redefinition-of-default-template-parameter
* - https://developercommunity.visualstudio.com/content/problem/425978/incorrect-c4348-warning-in-nested-template-declara.html * - https://developercommunity.visualstudio.com/content/problem/425978/incorrect-c4348-warning-in-nested-template-declara.html
*/ */

View File

@ -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);
} }

View File

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

View File

@ -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);
} }
}; };

View File

@ -45,14 +45,17 @@ constexpr char const * pf(long double) { return "%Lf" ; }
} // internal-linkage } // internal-linkage
template <typename T>
struct hash : public std::hash<T> {};
template <typename Key, typename T> template <typename Key, typename T>
using unordered_map = std::unordered_map< using unordered_map = std::unordered_map<
Key, T, std::hash<Key>, std::equal_to<Key>, ipc::mem::allocator<std::pair<const Key, T>> Key, T, ipc::hash<Key>, std::equal_to<Key>, ipc::mem::allocator<std::pair<Key const, T>>
>; >;
template <typename Key, typename T> template <typename Key, typename T>
using map = std::map< using map = std::map<
Key, T, std::less<Key>, ipc::mem::allocator<std::pair<const Key, T>> Key, T, std::less<Key>, ipc::mem::allocator<std::pair<Key const, T>>
>; >;
template <typename Char> template <typename Char>
@ -63,6 +66,18 @@ using basic_string = std::basic_string<
using string = basic_string<char>; using string = basic_string<char>;
using wstring = basic_string<wchar_t>; using wstring = basic_string<wchar_t>;
template <> struct hash<string> {
std::size_t operator()(string const &val) const noexcept {
return std::hash<char const *>{}(val.c_str());
}
};
template <> struct hash<wstring> {
std::size_t operator()(wstring const &val) const noexcept {
return std::hash<wchar_t const *>{}(val.c_str());
}
};
template <typename T> template <typename T>
ipc::string to_string(T val) { ipc::string to_string(T val) {
char buf[std::numeric_limits<T>::digits10 + 1] {}; char buf[std::numeric_limits<T>::digits10 + 1] {};
@ -72,4 +87,24 @@ ipc::string to_string(T val) {
return {}; return {};
} }
/// \brief Check string validity.
constexpr bool is_valid_string(char const *str) noexcept {
return (str != nullptr) && (str[0] != '\0');
}
/// \brief Make a valid string.
inline ipc::string make_string(char const *str) {
return is_valid_string(str) ? ipc::string{str} : ipc::string{};
}
/// \brief Combine prefix from a list of strings.
inline ipc::string make_prefix(ipc::string prefix, std::initializer_list<ipc::string> args) {
prefix += "__IPC_SHM__";
for (auto const &txt: args) {
if (txt.empty()) continue;
prefix += txt;
}
return prefix;
}
} // namespace ipc } // namespace ipc

View File

@ -1,4 +1,24 @@
#pragma once #ifndef LIBIPC_SRC_PLATFORM_DETAIL_H_
#define LIBIPC_SRC_PLATFORM_DETAIL_H_
// detect platform
#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || \
defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || \
defined(WINCE) || defined(_WIN32_WCE)
# define IPC_OS_WINDOWS_
#elif defined(__linux__) || defined(__linux)
# define IPC_OS_LINUX_
#elif defined(__FreeBSD__)
# define IPC_OS_FREEBSD_
#elif defined(__QNX__)
# define IPC_OS_QNX_
#elif defined(__APPLE__)
#elif defined(__ANDROID__)
// TBD
#endif
#if defined(__cplusplus)
#include <memory> #include <memory>
#include <mutex> #include <mutex>
@ -52,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 {
@ -111,17 +125,8 @@ constexpr const T& (min)(const T& a, const T& b) {
#endif/*__cplusplus < 201703L*/ #endif/*__cplusplus < 201703L*/
template <typename T, typename U>
auto horrible_cast(U rhs) noexcept
-> typename std::enable_if<std::is_trivially_copyable<T>::value
&& std::is_trivially_copyable<U>::value, T>::type {
union {
T t;
U u;
} r = {};
r.u = rhs;
return r.t;
}
} // namespace detail } // namespace detail
} // namespace ipc } // namespace ipc
#endif // defined(__cplusplus)
#endif // LIBIPC_SRC_PLATFORM_DETAIL_H_

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,172 +1,227 @@
#include <sys/shm.h> #include <sys/stat.h>
#include <sys/stat.h> #include <sys/mman.h>
#include <sys/mman.h> #include <sys/types.h>
#include <sys/types.h> #include <unistd.h>
#include <unistd.h> #include <fcntl.h>
#include <fcntl.h> #include <errno.h>
#include <errno.h>
#include <atomic>
#include <atomic> #include <string>
#include <string> #include <utility>
#include <utility> #include <cstring>
#include <cstring>
#include "libipc/shm.h"
#include "libipc/shm.h" #include "libipc/def.h"
#include "libipc/def.h" #include "libipc/pool_alloc.h"
#include "libipc/pool_alloc.h"
#include "libipc/utility/log.h"
#include "libipc/utility/log.h" #include "libipc/memory/resource.h"
#include "libipc/memory/resource.h"
namespace {
namespace {
struct info_t {
struct info_t { std::atomic<std::int32_t> acc_;
std::atomic_size_t acc_; };
};
struct id_info_t {
struct id_info_t { int fd_ = -1;
int fd_ = -1; void* mem_ = nullptr;
void* mem_ = nullptr; std::size_t size_ = 0;
std::size_t size_ = 0; ipc::string name_;
ipc::string name_; };
};
constexpr std::size_t calc_size(std::size_t size) {
constexpr std::size_t calc_size(std::size_t size) { return ((((size - 1) / alignof(info_t)) + 1) * alignof(info_t)) + sizeof(info_t);
return ((((size - 1) / alignof(info_t)) + 1) * alignof(info_t)) + sizeof(info_t); }
}
inline auto& acc_of(void* mem, std::size_t size) {
inline auto& acc_of(void* mem, std::size_t size) { return reinterpret_cast<info_t*>(static_cast<ipc::byte_t*>(mem) + size - sizeof(info_t))->acc_;
return reinterpret_cast<info_t*>(static_cast<ipc::byte_t*>(mem) + size - sizeof(info_t))->acc_; }
}
} // internal-linkage
} // internal-linkage
namespace ipc {
namespace ipc { namespace shm {
namespace shm {
id_t acquire(char const * name, std::size_t size, unsigned mode) {
id_t acquire(char const * name, std::size_t size, unsigned mode) { if (!is_valid_string(name)) {
if (name == nullptr || name[0] == '\0') { ipc::error("fail acquire: name is empty\n");
ipc::error("fail acquire: name is empty\n"); return nullptr;
return nullptr; }
} // For portable use, a shared memory object should be identified by name of the form /somename.
ipc::string op_name = ipc::string{"__IPC_SHM__"} + name; // see: https://man7.org/linux/man-pages/man3/shm_open.3.html
// Open the object for read-write access. ipc::string op_name;
int flag = O_RDWR; if (name[0] == '/') {
switch (mode) { op_name = name;
case open: } else {
size = 0; op_name = ipc::string{"/"} + name;
break; }
// The check for the existence of the object, // Open the object for read-write access.
// and its creation if it does not exist, are performed atomically. int flag = O_RDWR;
case create: switch (mode) {
flag |= O_CREAT | O_EXCL; case open:
break; size = 0;
// Create the shared memory object if it does not exist. break;
default: // The check for the existence of the object,
flag |= O_CREAT; // and its creation if it does not exist, are performed atomically.
break; case create:
} flag |= O_CREAT | O_EXCL;
int fd = ::shm_open(op_name.c_str(), flag, S_IRUSR | S_IWUSR | break;
S_IRGRP | S_IWGRP | // Create the shared memory object if it does not exist.
S_IROTH | S_IWOTH); default:
if (fd == -1) { flag |= O_CREAT;
ipc::error("fail shm_open[%d]: %s\n", errno, name); break;
return nullptr; }
} int fd = ::shm_open(op_name.c_str(), flag, S_IRUSR | S_IWUSR |
auto ii = mem::alloc<id_info_t>(); S_IRGRP | S_IWGRP |
ii->fd_ = fd; S_IROTH | S_IWOTH);
ii->size_ = size; if (fd == -1) {
ii->name_ = std::move(op_name); // only open shm not log error when file not exist
return ii; if (open != mode || ENOENT != errno) {
} ipc::error("fail shm_open[%d]: %s\n", errno, op_name.c_str());
}
void * get_mem(id_t id, std::size_t * size) { return nullptr;
if (id == nullptr) { }
ipc::error("fail get_mem: invalid id (null)\n"); ::fchmod(fd, S_IRUSR | S_IWUSR |
return nullptr; S_IRGRP | S_IWGRP |
} S_IROTH | S_IWOTH);
auto ii = static_cast<id_info_t*>(id); auto ii = mem::alloc<id_info_t>();
if (ii->mem_ != nullptr) { ii->fd_ = fd;
if (size != nullptr) *size = ii->size_; ii->size_ = size;
return ii->mem_; ii->name_ = std::move(op_name);
} return ii;
int fd = ii->fd_; }
if (fd == -1) {
ipc::error("fail to_mem: invalid id (fd = -1)\n"); std::int32_t get_ref(id_t id) {
return nullptr; if (id == nullptr) {
} return 0;
if (ii->size_ == 0) { }
struct stat st; auto ii = static_cast<id_info_t*>(id);
if (::fstat(fd, &st) != 0) { if (ii->mem_ == nullptr || ii->size_ == 0) {
ipc::error("fail fstat[%d]: %s, size = %zd\n", errno, ii->name_.c_str(), ii->size_); return 0;
return nullptr; }
} return acc_of(ii->mem_, ii->size_).load(std::memory_order_acquire);
ii->size_ = static_cast<std::size_t>(st.st_size); }
if ((ii->size_ <= sizeof(info_t)) || (ii->size_ % sizeof(info_t))) {
ipc::error("fail to_mem: %s, invalid size = %zd\n", ii->name_.c_str(), ii->size_); void sub_ref(id_t id) {
return nullptr; if (id == nullptr) {
} ipc::error("fail sub_ref: invalid id (null)\n");
} return;
else { }
ii->size_ = calc_size(ii->size_); auto ii = static_cast<id_info_t*>(id);
if (::ftruncate(fd, static_cast<off_t>(ii->size_)) != 0) { if (ii->mem_ == nullptr || ii->size_ == 0) {
ipc::error("fail ftruncate[%d]: %s, size = %zd\n", errno, ii->name_.c_str(), ii->size_); ipc::error("fail sub_ref: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
return nullptr; return;
} }
} acc_of(ii->mem_, ii->size_).fetch_sub(1, std::memory_order_acq_rel);
void* mem = ::mmap(nullptr, ii->size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); }
if (mem == MAP_FAILED) {
ipc::error("fail mmap[%d]: %s, size = %zd\n", errno, ii->name_.c_str(), ii->size_); void * get_mem(id_t id, std::size_t * size) {
return nullptr; if (id == nullptr) {
} ipc::error("fail get_mem: invalid id (null)\n");
::close(fd); return nullptr;
ii->fd_ = -1; }
ii->mem_ = mem; auto ii = static_cast<id_info_t*>(id);
if (size != nullptr) *size = ii->size_; if (ii->mem_ != nullptr) {
acc_of(mem, ii->size_).fetch_add(1, std::memory_order_release); if (size != nullptr) *size = ii->size_;
return mem; return ii->mem_;
} }
int fd = ii->fd_;
void release(id_t id) { if (fd == -1) {
if (id == nullptr) { ipc::error("fail get_mem: invalid id (fd = -1)\n");
ipc::error("fail release: invalid id (null)\n"); return nullptr;
return; }
} if (ii->size_ == 0) {
auto ii = static_cast<id_info_t*>(id); struct stat st;
if (ii->mem_ == nullptr || ii->size_ == 0) { if (::fstat(fd, &st) != 0) {
ipc::error("fail release: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_); ipc::error("fail fstat[%d]: %s, size = %zd\n", errno, ii->name_.c_str(), ii->size_);
} return nullptr;
else if (acc_of(ii->mem_, ii->size_).fetch_sub(1, std::memory_order_acquire) == 1) { }
::munmap(ii->mem_, ii->size_); ii->size_ = static_cast<std::size_t>(st.st_size);
if (!ii->name_.empty()) { if ((ii->size_ <= sizeof(info_t)) || (ii->size_ % sizeof(info_t))) {
::shm_unlink(ii->name_.c_str()); ipc::error("fail get_mem: %s, invalid size = %zd\n", ii->name_.c_str(), ii->size_);
} return nullptr;
} }
else ::munmap(ii->mem_, ii->size_); }
mem::free(ii); else {
} ii->size_ = calc_size(ii->size_);
if (::ftruncate(fd, static_cast<off_t>(ii->size_)) != 0) {
void remove(id_t id) { ipc::error("fail ftruncate[%d]: %s, size = %zd\n", errno, ii->name_.c_str(), ii->size_);
if (id == nullptr) { return nullptr;
ipc::error("fail remove: invalid id (null)\n"); }
return; }
} void* mem = ::mmap(nullptr, ii->size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
auto ii = static_cast<id_info_t*>(id); if (mem == MAP_FAILED) {
auto name = std::move(ii->name_); ipc::error("fail mmap[%d]: %s, size = %zd\n", errno, ii->name_.c_str(), ii->size_);
release(id); return nullptr;
if (!name.empty()) { }
::shm_unlink(name.c_str()); ::close(fd);
} ii->fd_ = -1;
} ii->mem_ = mem;
if (size != nullptr) *size = ii->size_;
void remove(char const * name) { acc_of(mem, ii->size_).fetch_add(1, std::memory_order_release);
if (name == nullptr || name[0] == '\0') { return mem;
ipc::error("fail remove: name is empty\n"); }
return;
} std::int32_t release(id_t id) noexcept {
::shm_unlink((ipc::string{"__IPC_SHM__"} + name).c_str()); if (id == nullptr) {
} ipc::error("fail release: invalid id (null)\n");
return -1;
} // namespace shm }
} // namespace ipc std::int32_t ret = -1;
auto ii = static_cast<id_info_t*>(id);
if (ii->mem_ == nullptr || ii->size_ == 0) {
ipc::error("fail release: invalid id (mem = %p, size = %zd), name = %s\n",
ii->mem_, ii->size_, ii->name_.c_str());
}
else if ((ret = acc_of(ii->mem_, ii->size_).fetch_sub(1, std::memory_order_acq_rel)) <= 1) {
::munmap(ii->mem_, ii->size_);
if (!ii->name_.empty()) {
int unlink_ret = ::shm_unlink(ii->name_.c_str());
if (unlink_ret == -1) {
ipc::error("fail shm_unlink[%d]: %s\n", errno, ii->name_.c_str());
}
}
}
else ::munmap(ii->mem_, ii->size_);
mem::free(ii);
return ret;
}
void remove(id_t id) noexcept {
if (id == nullptr) {
ipc::error("fail remove: invalid id (null)\n");
return;
}
auto ii = static_cast<id_info_t*>(id);
auto name = std::move(ii->name_);
release(id);
if (!name.empty()) {
int unlink_ret = ::shm_unlink(name.c_str());
if (unlink_ret == -1) {
ipc::error("fail shm_unlink[%d]: %s\n", errno, name.c_str());
}
}
}
void remove(char const * name) noexcept {
if (!is_valid_string(name)) {
ipc::error("fail remove: name is empty\n");
return;
}
// For portable use, a shared memory object should be identified by name of the form /somename.
ipc::string op_name;
if (name[0] == '/') {
op_name = name;
} else {
op_name = ipc::string{"/"} + name;
}
int unlink_ret = ::shm_unlink(op_name.c_str());
if (unlink_ret == -1) {
ipc::error("fail shm_unlink[%d]: %s\n", errno, op_name.c_str());
}
}
} // namespace shm
} // namespace ipc

View File

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

View File

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

View File

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

View File

@ -0,0 +1,133 @@
#pragma once
#include <cstdint>
#include <string>
#include <mutex>
#if defined(__MINGW32__)
#include <windows.h>
#else
#include <Windows.h>
#endif
#include "libipc/utility/log.h"
#include "libipc/utility/scope_guard.h"
#include "libipc/platform/detail.h"
#include "libipc/mutex.h"
#include "libipc/semaphore.h"
#include "libipc/shm.h"
namespace ipc {
namespace detail {
namespace sync {
class condition {
ipc::sync::semaphore sem_;
ipc::sync::mutex lock_;
ipc::shm::handle shm_;
std::int32_t &counter() {
return *static_cast<std::int32_t *>(shm_.get());
}
public:
condition() = default;
~condition() noexcept = default;
auto native() noexcept {
return sem_.native();
}
auto native() const noexcept {
return sem_.native();
}
bool valid() const noexcept {
return sem_.valid() && lock_.valid() && shm_.valid();
}
bool open(char const *name) noexcept {
close();
if (!sem_.open((std::string{name} + "_COND_SEM_").c_str())) {
return false;
}
auto finally_sem = ipc::guard([this] { sem_.close(); }); // close when failed
if (!lock_.open((std::string{name} + "_COND_LOCK_").c_str())) {
return false;
}
auto finally_lock = ipc::guard([this] { lock_.close(); }); // close when failed
if (!shm_.acquire((std::string{name} + "_COND_SHM_").c_str(), sizeof(std::int32_t))) {
return false;
}
finally_lock.dismiss();
finally_sem.dismiss();
return valid();
}
void close() noexcept {
if (!valid()) return;
sem_.close();
lock_.close();
shm_.release();
}
void clear() noexcept {
close();
}
static void clear_storage(char const *name) noexcept {
ipc::shm::handle::clear_storage(name);
ipc::sync::mutex::clear_storage(name);
ipc::sync::semaphore::clear_storage(name);
}
bool wait(ipc::sync::mutex &mtx, std::uint64_t tm) noexcept {
if (!valid()) return false;
auto &cnt = counter();
{
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> guard {lock_};
cnt = (cnt < 0) ? 1 : cnt + 1;
}
DWORD ms = (tm == invalid_value) ? INFINITE : static_cast<DWORD>(tm);
/**
* \see
* - https://www.microsoft.com/en-us/research/wp-content/uploads/2004/12/ImplementingCVs.pdf
* - https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-signalobjectandwait
*/
bool rs = ::SignalObjectAndWait(mtx.native(), sem_.native(), ms, FALSE) == WAIT_OBJECT_0;
bool rl = mtx.lock(); // INFINITE
if (!rs) {
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> guard {lock_};
cnt -= 1;
}
return rs && rl;
}
bool notify(ipc::sync::mutex &) noexcept {
if (!valid()) return false;
auto &cnt = counter();
if (!lock_.lock()) return false;
bool ret = false;
if (cnt > 0) {
ret = sem_.post(1);
cnt -= 1;
}
return lock_.unlock() && ret;
}
bool broadcast(ipc::sync::mutex &) noexcept {
if (!valid()) return false;
auto &cnt = counter();
if (!lock_.lock()) return false;
bool ret = false;
if (cnt > 0) {
ret = sem_.post(cnt);
cnt = 0;
}
return lock_.unlock() && ret;
}
};
} // namespace sync
} // namespace detail
} // namespace ipc

View File

@ -0,0 +1,109 @@
#pragma once
#include <cstdint>
#include <system_error>
#if defined(__MINGW32__)
#include <windows.h>
#else
#include <Windows.h>
#endif
#include "libipc/utility/log.h"
#include "to_tchar.h"
#include "get_sa.h"
namespace ipc {
namespace detail {
namespace sync {
class mutex {
HANDLE h_ = NULL;
public:
mutex() noexcept = default;
~mutex() noexcept = default;
static void init() {}
HANDLE native() const noexcept {
return h_;
}
bool valid() const noexcept {
return h_ != NULL;
}
bool open(char const *name) noexcept {
close();
h_ = ::CreateMutex(detail::get_sa(), FALSE, detail::to_tchar(name).c_str());
if (h_ == NULL) {
ipc::error("fail CreateMutex[%lu]: %s\n", ::GetLastError(), name);
return false;
}
return true;
}
void close() noexcept {
if (!valid()) return;
::CloseHandle(h_);
h_ = NULL;
}
void clear() noexcept {
close();
}
static void clear_storage(char const */*name*/) noexcept {
}
bool lock(std::uint64_t tm) noexcept {
DWORD ret, ms = (tm == invalid_value) ? INFINITE : static_cast<DWORD>(tm);
for(;;) {
switch ((ret = ::WaitForSingleObject(h_, ms))) {
case WAIT_OBJECT_0:
return true;
case WAIT_TIMEOUT:
return false;
case WAIT_ABANDONED:
ipc::log("fail WaitForSingleObject[%lu]: WAIT_ABANDONED, try again.\n", ::GetLastError());
if (!unlock()) {
return false;
}
break; // loop again
default:
ipc::error("fail WaitForSingleObject[%lu]: 0x%08X\n", ::GetLastError(), ret);
return false;
}
}
}
bool try_lock() noexcept(false) {
DWORD ret = ::WaitForSingleObject(h_, 0);
switch (ret) {
case WAIT_OBJECT_0:
return true;
case WAIT_TIMEOUT:
return false;
case WAIT_ABANDONED:
unlock();
IPC_FALLTHROUGH_;
default:
ipc::error("fail WaitForSingleObject[%lu]: 0x%08X\n", ::GetLastError(), ret);
throw std::system_error{static_cast<int>(ret), std::system_category()};
}
}
bool unlock() noexcept {
if (!::ReleaseMutex(h_)) {
ipc::error("fail ReleaseMutex[%lu]\n", ::GetLastError());
return false;
}
return true;
}
};
} // namespace sync
} // namespace detail
} // namespace ipc

View File

@ -0,0 +1,85 @@
#pragma once
#include <cstdint>
#if defined(__MINGW32__)
#include <windows.h>
#else
#include <Windows.h>
#endif
#include "libipc/utility/log.h"
#include "to_tchar.h"
#include "get_sa.h"
namespace ipc {
namespace detail {
namespace sync {
class semaphore {
HANDLE h_ = NULL;
public:
semaphore() noexcept = default;
~semaphore() noexcept = default;
HANDLE native() const noexcept {
return h_;
}
bool valid() const noexcept {
return h_ != NULL;
}
bool open(char const *name, std::uint32_t count) noexcept {
close();
h_ = ::CreateSemaphore(detail::get_sa(),
static_cast<LONG>(count), LONG_MAX,
detail::to_tchar(name).c_str());
if (h_ == NULL) {
ipc::error("fail CreateSemaphore[%lu]: %s\n", ::GetLastError(), name);
return false;
}
return true;
}
void close() noexcept {
if (!valid()) return;
::CloseHandle(h_);
h_ = NULL;
}
void clear() noexcept {
close();
}
static void clear_storage(char const */*name*/) noexcept {
}
bool wait(std::uint64_t tm) noexcept {
DWORD ret, ms = (tm == invalid_value) ? INFINITE : static_cast<DWORD>(tm);
switch ((ret = ::WaitForSingleObject(h_, ms))) {
case WAIT_OBJECT_0:
return true;
case WAIT_TIMEOUT:
return false;
case WAIT_ABANDONED:
default:
ipc::error("fail WaitForSingleObject[%lu]: 0x%08X\n", ::GetLastError(), ret);
return false;
}
}
bool post(std::uint32_t count) noexcept {
if (!::ReleaseSemaphore(h_, static_cast<LONG>(count), NULL)) {
ipc::error("fail ReleaseSemaphore[%lu]\n", ::GetLastError());
return false;
}
return true;
}
};
} // namespace sync
} // namespace detail
} // namespace ipc

View File

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

View File

@ -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>
@ -11,8 +15,8 @@
#include <cstddef> #include <cstddef>
#include "libipc/utility/concept.h" #include "libipc/utility/concept.h"
#include "libipc/platform/detail.h"
#include "libipc/memory/resource.h" #include "libipc/memory/resource.h"
#include "libipc/platform/detail.h"
namespace ipc { namespace ipc {
namespace detail { namespace detail {
@ -41,8 +45,8 @@ constexpr auto to_tchar(ipc::string &&str) -> IsSameChar<T, ipc::string, ipc::st
} }
/** /**
* codecvt_utf8_utf16/std::wstring_convert is deprecated * \remarks codecvt_utf8_utf16/std::wstring_convert is deprecated
* @see https://codingtidbit.com/2020/02/09/c17-codecvt_utf8-is-deprecated/ * \see https://codingtidbit.com/2020/02/09/c17-codecvt_utf8-is-deprecated/
* https://stackoverflow.com/questions/42946335/deprecated-header-codecvt-replacement * https://stackoverflow.com/questions/42946335/deprecated-header-codecvt-replacement
* https://en.cppreference.com/w/cpp/locale/codecvt/in * https://en.cppreference.com/w/cpp/locale/codecvt/in
* https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar * https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar

View File

@ -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);
} }

View File

@ -18,6 +18,7 @@
#include "libipc/utility/log.h" #include "libipc/utility/log.h"
#include "libipc/platform/detail.h" #include "libipc/platform/detail.h"
#include "libipc/circ/elem_def.h" #include "libipc/circ/elem_def.h"
#include "libipc/memory/resource.h"
namespace ipc { namespace ipc {
namespace detail { namespace detail {
@ -29,7 +30,7 @@ protected:
template <typename Elems> template <typename Elems>
Elems* open(char const * name) { Elems* open(char const * name) {
if (name == nullptr || name[0] == '\0') { if (!is_valid_string(name)) {
ipc::error("fail open waiter: name is empty!\n"); ipc::error("fail open waiter: name is empty!\n");
return nullptr; return nullptr;
} }
@ -54,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 {
@ -68,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;
} }
@ -103,7 +113,7 @@ public:
explicit queue_base(char const * name) explicit queue_base(char const * name)
: queue_base{} { : queue_base{} {
elems_ = open<elems_t>(name); elems_ = queue_conn::template open<elems_t>(name);
} }
explicit queue_base(elems_t * elems) noexcept explicit queue_base(elems_t * elems) noexcept
@ -116,6 +126,17 @@ public:
base_t::close(); base_t::close();
} }
bool open(char const * name) noexcept {
base_t::close();
elems_ = queue_conn::template open<elems_t>(name);
return elems_ != nullptr;
}
void clear() noexcept {
base_t::clear();
elems_ = nullptr;
}
elems_t * elems() noexcept { return elems_; } elems_t * elems() noexcept { return elems_; }
elems_t const * elems() const noexcept { return elems_; } elems_t const * elems() const noexcept { return elems_; }
@ -130,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)) {
@ -144,7 +169,7 @@ public:
} }
std::size_t conn_count() const noexcept { std::size_t conn_count() const noexcept {
return (elems_ == nullptr) ? invalid_value : elems_->conn_count(); return (elems_ == nullptr) ? static_cast<std::size_t>(invalid_value) : elems_->conn_count();
} }
bool valid() const noexcept { bool valid() const noexcept {

View File

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

View File

@ -0,0 +1,85 @@
#include "libipc/condition.h"
#include "libipc/utility/pimpl.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/platform/detail.h"
#if defined(IPC_OS_WINDOWS_)
#include "libipc/platform/win/condition.h"
#elif defined(IPC_OS_LINUX_)
#include "libipc/platform/linux/condition.h"
#elif defined(IPC_OS_QNX_) || defined(IPC_OS_FREEBSD_)
#include "libipc/platform/posix/condition.h"
#else/*IPC_OS*/
# error "Unsupported platform."
#endif
namespace ipc {
namespace sync {
class condition::condition_ : public ipc::pimpl<condition_> {
public:
ipc::detail::sync::condition cond_;
};
condition::condition()
: p_(p_->make()) {
}
condition::condition(char const * name)
: condition() {
open(name);
}
condition::~condition() {
close();
p_->clear();
}
void const *condition::native() const noexcept {
return impl(p_)->cond_.native();
}
void *condition::native() noexcept {
return impl(p_)->cond_.native();
}
bool condition::valid() const noexcept {
return impl(p_)->cond_.valid();
}
bool condition::open(char const *name) noexcept {
if (!is_valid_string(name)) {
ipc::error("fail condition open: name is empty\n");
return false;
}
return impl(p_)->cond_.open(name);
}
void condition::close() noexcept {
impl(p_)->cond_.close();
}
void condition::clear() noexcept {
impl(p_)->cond_.clear();
}
void condition::clear_storage(char const * name) noexcept {
ipc::detail::sync::condition::clear_storage(name);
}
bool condition::wait(ipc::sync::mutex &mtx, std::uint64_t tm) noexcept {
return impl(p_)->cond_.wait(mtx, tm);
}
bool condition::notify(ipc::sync::mutex &mtx) noexcept {
return impl(p_)->cond_.notify(mtx);
}
bool condition::broadcast(ipc::sync::mutex &mtx) noexcept {
return impl(p_)->cond_.broadcast(mtx);
}
} // namespace sync
} // namespace ipc

85
src/libipc/sync/mutex.cpp Normal file
View File

@ -0,0 +1,85 @@
#include "libipc/mutex.h"
#include "libipc/utility/pimpl.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/platform/detail.h"
#if defined(IPC_OS_WINDOWS_)
#include "libipc/platform/win/mutex.h"
#elif defined(IPC_OS_LINUX_)
#include "libipc/platform/linux/mutex.h"
#elif defined(IPC_OS_QNX_) || defined(IPC_OS_FREEBSD_)
#include "libipc/platform/posix/mutex.h"
#else/*IPC_OS*/
# error "Unsupported platform."
#endif
namespace ipc {
namespace sync {
class mutex::mutex_ : public ipc::pimpl<mutex_> {
public:
ipc::detail::sync::mutex lock_;
};
mutex::mutex()
: p_(p_->make()) {
}
mutex::mutex(char const * name)
: mutex() {
open(name);
}
mutex::~mutex() {
close();
p_->clear();
}
void const *mutex::native() const noexcept {
return impl(p_)->lock_.native();
}
void *mutex::native() noexcept {
return impl(p_)->lock_.native();
}
bool mutex::valid() const noexcept {
return impl(p_)->lock_.valid();
}
bool mutex::open(char const *name) noexcept {
if (!is_valid_string(name)) {
ipc::error("fail mutex open: name is empty\n");
return false;
}
return impl(p_)->lock_.open(name);
}
void mutex::close() noexcept {
impl(p_)->lock_.close();
}
void mutex::clear() noexcept {
impl(p_)->lock_.clear();
}
void mutex::clear_storage(char const * name) noexcept {
ipc::detail::sync::mutex::clear_storage(name);
}
bool mutex::lock(std::uint64_t tm) noexcept {
return impl(p_)->lock_.lock(tm);
}
bool mutex::try_lock() noexcept(false) {
return impl(p_)->lock_.try_lock();
}
bool mutex::unlock() noexcept {
return impl(p_)->lock_.unlock();
}
} // namespace sync
} // namespace ipc

View File

@ -0,0 +1,79 @@
#include "libipc/semaphore.h"
#include "libipc/utility/pimpl.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/platform/detail.h"
#if defined(IPC_OS_WINDOWS_)
#include "libipc/platform/win/semaphore.h"
#elif defined(IPC_OS_LINUX_) || defined(IPC_OS_QNX_) || defined(IPC_OS_FREEBSD_)
#include "libipc/platform/posix/semaphore_impl.h"
#else/*IPC_OS*/
# error "Unsupported platform."
#endif
namespace ipc {
namespace sync {
class semaphore::semaphore_ : public ipc::pimpl<semaphore_> {
public:
ipc::detail::sync::semaphore sem_;
};
semaphore::semaphore()
: p_(p_->make()) {
}
semaphore::semaphore(char const * name, std::uint32_t count)
: semaphore() {
open(name, count);
}
semaphore::~semaphore() {
close();
p_->clear();
}
void const *semaphore::native() const noexcept {
return impl(p_)->sem_.native();
}
void *semaphore::native() noexcept {
return impl(p_)->sem_.native();
}
bool semaphore::valid() const noexcept {
return impl(p_)->sem_.valid();
}
bool semaphore::open(char const *name, std::uint32_t count) noexcept {
if (!is_valid_string(name)) {
ipc::error("fail semaphore open: name is empty\n");
return false;
}
return impl(p_)->sem_.open(name, count);
}
void semaphore::close() noexcept {
impl(p_)->sem_.close();
}
void semaphore::clear() noexcept {
impl(p_)->sem_.clear();
}
void semaphore::clear_storage(char const * name) noexcept {
ipc::detail::sync::semaphore::clear_storage(name);
}
bool semaphore::wait(std::uint64_t tm) noexcept {
return impl(p_)->sem_.wait(tm);
}
bool semaphore::post(std::uint32_t count) noexcept {
return impl(p_)->sem_.post(count);
}
} // namespace sync
} // namespace ipc

View File

@ -0,0 +1,22 @@
#include "libipc/waiter.h"
#include "libipc/platform/detail.h"
#if defined(IPC_OS_WINDOWS_)
#include "libipc/platform/win/mutex.h"
#elif defined(IPC_OS_LINUX_)
#include "libipc/platform/linux/mutex.h"
#elif defined(IPC_OS_QNX_) || defined(IPC_OS_FREEBSD_)
#include "libipc/platform/posix/mutex.h"
#else/*IPC_OS*/
# error "Unsupported platform."
#endif
namespace ipc {
namespace detail {
void waiter::init() {
ipc::detail::sync::mutex::init();
}
} // namespace detail
} // namespace ipc

View File

@ -3,6 +3,7 @@
#include <new> #include <new>
#include <utility> #include <utility>
#include "libipc/platform/detail.h"
#include "libipc/utility/concept.h" #include "libipc/utility/concept.h"
#include "libipc/pool_alloc.h" #include "libipc/pool_alloc.h"
@ -17,49 +18,45 @@ template <typename T, typename R = T*>
using IsImplUncomfortable = ipc::require<(sizeof(T) > sizeof(T*)), R>; using IsImplUncomfortable = ipc::require<(sizeof(T) > sizeof(T*)), R>;
template <typename T, typename... P> template <typename T, typename... P>
constexpr auto make_impl(P&&... params) -> IsImplComfortable<T> { IPC_CONSTEXPR_ auto make_impl(P&&... params) -> IsImplComfortable<T> {
T* buf {}; T* buf {};
::new (&buf) T { std::forward<P>(params)... }; ::new (&buf) T { std::forward<P>(params)... };
return buf; return buf;
} }
template <typename T> template <typename T>
constexpr auto impl(T* const (& p)) -> IsImplComfortable<T> { IPC_CONSTEXPR_ auto impl(T* const (& p)) -> IsImplComfortable<T> {
return reinterpret_cast<T*>(&const_cast<char &>(reinterpret_cast<char const &>(p))); return reinterpret_cast<T*>(&const_cast<char &>(reinterpret_cast<char const &>(p)));
} }
template <typename T> template <typename T>
constexpr auto clear_impl(T* p) -> IsImplComfortable<T, void> { IPC_CONSTEXPR_ auto clear_impl(T* p) -> IsImplComfortable<T, void> {
if (p != nullptr) impl(p)->~T(); if (p != nullptr) impl(p)->~T();
} }
template <typename T, typename... P> template <typename T, typename... P>
constexpr auto make_impl(P&&... params) -> IsImplUncomfortable<T> { IPC_CONSTEXPR_ auto make_impl(P&&... params) -> IsImplUncomfortable<T> {
return mem::alloc<T>(std::forward<P>(params)...); return mem::alloc<T>(std::forward<P>(params)...);
} }
template <typename T> template <typename T>
constexpr auto clear_impl(T* p) -> IsImplUncomfortable<T, void> { IPC_CONSTEXPR_ auto clear_impl(T* p) -> IsImplUncomfortable<T, void> {
mem::free(p); mem::free(p);
} }
template <typename T> template <typename T>
constexpr auto impl(T* const (& p)) -> IsImplUncomfortable<T> { IPC_CONSTEXPR_ auto impl(T* const (& p)) -> IsImplUncomfortable<T> {
return p; return p;
} }
template <typename T> template <typename T>
struct pimpl { struct pimpl {
template <typename... P> template <typename... P>
constexpr static T* make(P&&... params) { IPC_CONSTEXPR_ static T* make(P&&... params) {
return make_impl<T>(std::forward<P>(params)...); return make_impl<T>(std::forward<P>(params)...);
} }
#if __cplusplus >= 201703L IPC_CONSTEXPR_ void clear() {
constexpr void clear() {
#else /*__cplusplus < 201703L*/
void clear() {
#endif/*__cplusplus < 201703L*/
clear_impl(static_cast<T*>(const_cast<pimpl*>(this))); clear_impl(static_cast<T*>(const_cast<pimpl*>(this)));
} }
}; };

View File

@ -1,8 +1,9 @@
#pragma once #pragma once
#include <utility> // std::forward, std::integer_sequence #include <utility> // std::forward, std::integer_sequence
#include <cstddef> // std::size_t #include <cstddef> // std::size_t
#include <new> // std::hardware_destructive_interference_size #include <new> // std::hardware_destructive_interference_size
#include <type_traits> // std::is_trivially_copyable
#include "libipc/platform/detail.h" #include "libipc/platform/detail.h"
@ -44,13 +45,15 @@ enum {
}; };
template <typename T, typename U> template <typename T, typename U>
T horrible_cast(U val) { auto horrible_cast(U rhs) noexcept
-> typename std::enable_if<std::is_trivially_copyable<T>::value
&& std::is_trivially_copyable<U>::value, T>::type {
union { union {
T out; T t;
U in; U u;
} u; } r = {};
u.in = val; r.u = rhs;
return u.out; return r.t;
} }
IPC_CONSTEXPR_ std::size_t make_align(std::size_t align, std::size_t size) { IPC_CONSTEXPR_ std::size_t make_align(std::size_t align, std::size_t size) {

97
src/libipc/waiter.h Normal file
View File

@ -0,0 +1,97 @@
#pragma once
#include <utility>
#include <string>
#include <mutex>
#include <atomic>
#include "libipc/def.h"
#include "libipc/mutex.h"
#include "libipc/condition.h"
#include "libipc/platform/detail.h"
namespace ipc {
namespace detail {
class waiter {
ipc::sync::condition cond_;
ipc::sync::mutex lock_;
std::atomic<bool> quit_ {false};
public:
static void init();
waiter() = default;
waiter(char const *name) {
open(name);
}
~waiter() {
close();
}
bool valid() const noexcept {
return cond_.valid() && lock_.valid();
}
bool open(char const *name) noexcept {
quit_.store(false, std::memory_order_relaxed);
if (!cond_.open((std::string{name} + "_WAITER_COND_").c_str())) {
return false;
}
if (!lock_.open((std::string{name} + "_WAITER_LOCK_").c_str())) {
cond_.close();
return false;
}
return valid();
}
void close() noexcept {
cond_.close();
lock_.close();
}
void clear() noexcept {
cond_.clear();
lock_.clear();
}
static void clear_storage(char const *name) noexcept {
ipc::sync::condition::clear_storage((std::string{name} + "_WAITER_COND_").c_str());
ipc::sync::mutex::clear_storage((std::string{name} + "_WAITER_LOCK_").c_str());
}
template <typename F>
bool wait_if(F &&pred, std::uint64_t tm = ipc::invalid_value) noexcept {
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> guard {lock_};
while ([this, &pred] {
return !quit_.load(std::memory_order_relaxed)
&& std::forward<F>(pred)();
}()) {
if (!cond_.wait(lock_, tm)) return false;
}
return true;
}
bool notify() noexcept {
{
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> barrier{lock_}; // barrier
}
return cond_.notify(lock_);
}
bool broadcast() noexcept {
{
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> barrier{lock_}; // barrier
}
return cond_.broadcast(lock_);
}
bool quit_waiting() {
quit_.store(true, std::memory_order_release);
return broadcast();
}
};
} // namespace detail
} // namespace ipc

View File

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

View File

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

View File

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

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

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

28
test/archive/CMakeLists.txt.old Executable file
View 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)

View File

@ -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

View File

@ -18,8 +18,9 @@ using namespace ipc;
namespace { namespace {
constexpr int LoopCount = 10000; constexpr int LoopCount = 10000;
constexpr int MultiMax = 8; constexpr int MultiMax = 8;
constexpr int TestBuffMax = 65536;
struct msg_head { struct msg_head {
int id_; int id_;
@ -28,7 +29,7 @@ struct msg_head {
class rand_buf : public buffer { class rand_buf : public buffer {
public: public:
rand_buf() { rand_buf() {
int size = capo::random<>{sizeof(msg_head), 65536}(); int size = capo::random<>{(int)sizeof(msg_head), TestBuffMax}();
*this = buffer(new char[size], size, [](void * p, std::size_t) { *this = buffer(new char[size], size, [](void * p, std::size_t) {
delete [] static_cast<char *>(p); delete [] static_cast<char *>(p);
}); });
@ -109,10 +110,10 @@ void test_sr(char const * name, int s_cnt, int r_cnt) {
for (int k = 0; k < s_cnt; ++k) { for (int k = 0; k < s_cnt; ++k) {
ipc_ut::sender() << [name, &sw, r_cnt, k] { ipc_ut::sender() << [name, &sw, r_cnt, k] {
Que que { name, ipc::sender }; Que que { name, ipc::sender };
EXPECT_TRUE(que.wait_for_recv(r_cnt)); ASSERT_TRUE(que.wait_for_recv(r_cnt));
sw.start(); sw.start();
for (int i = 0; i < (int)data_set__.get().size(); ++i) { for (int i = 0; i < (int)data_set__.get().size(); ++i) {
EXPECT_TRUE(que.send(data_set__.get()[i])); ASSERT_TRUE(que.send(data_set__.get()[i]));
} }
}; };
} }
@ -132,7 +133,7 @@ void test_sr(char const * name, int s_cnt, int r_cnt) {
if (data_set != got) { if (data_set != got) {
printf("data_set__.get()[%d] != got, size = %zd/%zd\n", printf("data_set__.get()[%d] != got, size = %zd/%zd\n",
i, data_set.size(), got.size()); i, data_set.size(), got.size());
EXPECT_TRUE(false); ASSERT_TRUE(false);
} }
} }
}; };
@ -140,7 +141,7 @@ void test_sr(char const * name, int s_cnt, int r_cnt) {
ipc_ut::sender().wait_for_done(); ipc_ut::sender().wait_for_done();
Que que { name }; Que que { name };
EXPECT_TRUE(que.wait_for_recv(r_cnt)); ASSERT_TRUE(que.wait_for_recv(r_cnt));
for (int k = 0; k < r_cnt; ++k) { for (int k = 0; k < r_cnt; ++k) {
que.send(rand_buf{msg_head{-1}}); que.send(rand_buf{msg_head{-1}});
} }
@ -150,18 +151,66 @@ void test_sr(char const * name, int s_cnt, int r_cnt) {
} // internal-linkage } // internal-linkage
TEST(IPC, basic) { 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_basic<relat::single, relat::single, trans::unicast >("ssu"); test_basic<relat::single, relat::single, trans::unicast >("ssu");
test_basic<relat::single, relat::multi , trans::unicast >("smu"); }
test_basic<relat::multi , relat::multi , trans::unicast >("mmu");
TEST(IPC, basic_smb) {
test_basic<relat::single, relat::multi , trans::broadcast>("smb"); test_basic<relat::single, relat::multi , trans::broadcast>("smb");
}
TEST(IPC, basic_mmb) {
test_basic<relat::multi , relat::multi , trans::broadcast>("mmb"); test_basic<relat::multi , relat::multi , trans::broadcast>("mmb");
} }
TEST(IPC, 1v1) { TEST(IPC, 1v1) {
test_sr<relat::single, relat::single, trans::unicast >("ssu", 1, 1); test_sr<relat::single, relat::single, trans::unicast >("ssu", 1, 1);
test_sr<relat::single, relat::multi , trans::unicast >("smu", 1, 1); //test_sr<relat::single, relat::multi , trans::unicast >("smu", 1, 1);
test_sr<relat::multi , relat::multi , trans::unicast >("mmu", 1, 1); //test_sr<relat::multi , relat::multi , trans::unicast >("mmu", 1, 1);
test_sr<relat::single, relat::multi , trans::broadcast>("smb", 1, 1); test_sr<relat::single, relat::multi , trans::broadcast>("smb", 1, 1);
test_sr<relat::multi , relat::multi , trans::broadcast>("mmb", 1, 1); test_sr<relat::multi , relat::multi , trans::broadcast>("mmb", 1, 1);
} }

View File

@ -8,7 +8,7 @@
#include "test.h" #include "test.h"
#include "libipc/platform/to_tchar.h" #include "libipc/platform/win/to_tchar.h"
TEST(Platform, to_tchar) { TEST(Platform, to_tchar) {
char const *utf8 = "hello world, " char const *utf8 = "hello world, "

View File

@ -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) ;
EXPECT_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) {
EXPECT_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) {
EXPECT_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) {
EXPECT_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) {
EXPECT_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) {
EXPECT_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) {
EXPECT_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};
EXPECT_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) {
EXPECT_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};
EXPECT_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) {
EXPECT_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};
EXPECT_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) {
EXPECT_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};
EXPECT_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};
EXPECT_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) {
EXPECT_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};
EXPECT_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};
EXPECT_FALSE(que.connect()); ASSERT_FALSE(que.connect());
} }
EXPECT_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) {
EXPECT_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};
EXPECT_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};
EXPECT_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
View 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

212
test/archive/test_sync.cpp Normal file
View File

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

85
test/archive/test_waiter.cpp Executable file
View File

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

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