Compare commits

...

73 Commits

Author SHA1 Message Date
木头云
2815fe0163 fix(shm_win): Use mem::$delete instead of mem::free in release()
The acquire() function allocates id_info_t using mem::$new<id_info_t>(),
so the release() function must use mem::$delete(ii) to deallocate it,
not mem::free(ii). This ensures proper allocation/deallocation pairing.

Issue: Memory allocated with mem::$new must be freed with mem::$delete
to maintain consistent memory management semantics.
2025-12-03 08:48:02 +00:00
木头云
b35de5a154 fix(test): Update test.h include paths after master rebase
After rebasing onto master, test.h was moved to test/archive/.
Updated include paths in test subdirectories:
- test/imp/*.cpp: "test.h" -> "../archive/test.h"
- test/mem/*.cpp: "test.h" -> "../archive/test.h"
- test/concur/*.cpp: "test.h" -> "../archive/test.h"

This ensures all test files can properly find the test header
after the directory reorganization in master branch.
2025-12-03 08:28:37 +00:00
木头云
874e0b25bc fix(msvc): Fix C4138 warning by adding space before commented parameter names
ISSUE:
MSVC compiler reports warning C4138: '*/' found outside of comment
for patterns like 'void */*p*/' where the pointer asterisk is immediately
followed by a comment start.

AFFECTED FILES:
- include/libipc/mem/new.h (line 30)
- src/libipc/platform/win/mutex.h (line 54)
- src/libipc/platform/win/semaphore.h (line 53)

CHANGES:
Changed 'type */*param*/' to 'type * /*param*/' (added space before comment)

Examples:
- void */*p*/       → void * /*p*/
- char const */*name*/ → char const * /*name*/

This resolves the MSVC warning while maintaining code functionality
and keeping the commented-out parameter names for documentation.
2025-12-03 08:13:44 +00:00
木头云
52ff081770 fix(test): Fix buffer overflow in data_set caused by array placement new
ROOT CAUSE:
Array placement new (::new(buffer) T[N]) adds a hidden cookie (array size)
before the array elements in some compiler implementations (particularly MSVC).
The cookie is used for proper array destruction. However, the data_set buffer
was sized only for sizeof(T[N]), not accounting for the cookie overhead.

ISSUE:
- Buffer allocated: sizeof(rand_buf[LoopCount])
- Actual space needed: sizeof(cookie) + sizeof(rand_buf[LoopCount])
- Result: Cookie and part of array written beyond buffer boundary
- Consequence: Memory corruption, leading to invalid pointers in buffer objects

SYMPTOM:
In IPC.1v1 test, memcpy(buf, data, size) crashed because 'data' pointer
(from buffer::data()) pointed to corrupted/invalid memory address.

SOLUTION:
Replace array placement new with individual element placement new:
- Cast buffer to array pointer directly (no cookie needed)
- Construct each element individually with placement new
- Manually destroy each element in destructor

This approach:
- Eliminates cookie overhead
- Provides precise control over object lifetime
- Works consistently across all compilers

Fixes crash in IPC.1v1 test case on MSVC.
2025-12-03 08:13:44 +00:00
木头云
c9d0a94894 refactor(uninitialized): Improve construct() overload resolution
IMPROVEMENTS:
1. Add explicit zero-argument overload to avoid SFINAE ambiguity
2. Require at least one argument (A1) for parameterized overloads
3. Better separation between direct initialization and aggregate initialization

BENEFITS:
- Clearer intent: zero-argument construction is explicitly handled
- Avoids potential SFINAE ambiguity when empty parameter pack is used
- More maintainable: easier to understand which overload is selected
- Consistent with modern C++ best practices for variadic templates

TECHNICAL DETAILS:
- Zero-arg overload: Always uses T() for value initialization
- One-or-more-arg overload: Uses SFINAE to choose between:
  * T(args...) for types with matching constructor
  * T{args...} for aggregate types or types with initializer_list ctor

This is a code quality improvement and does not fix any compilation issues,
but provides better template overload resolution.
2025-12-03 08:13:44 +00:00
木头云
cf444e5309 fix(container_allocator): Fix MSVC compilation by correcting allocator semantics
ROOT CAUSE:
The allocate() function was incorrectly constructing objects during memory
allocation, violating C++ allocator requirements. MSVC's std::_Tree_node has
a deleted default constructor, causing compilation failure.

CHANGES:
- container_allocator::allocate() now only allocates raw memory without
  constructing objects (removed mem::$new and ipc::construct calls)
- container_allocator::deallocate() now only frees memory without
  destroying objects (removed mem::$delete and ipc::destroy_n calls)

WHY THIS FIXES THE ISSUE:
C++ allocator semantics require strict separation:
  * allocate()   -> raw memory allocation only
  * construct()  -> object construction with proper arguments
  * destroy()    -> object destruction
  * deallocate() -> memory deallocation only

Standard containers (like std::map) call construct() with proper arguments
(key, value) to initialize nodes, not allocate(). Since std::_Tree_node in
MSVC has no default constructor (= delete), attempting to construct it
without arguments always fails.

Fixes MSVC 2017 compilation error:
  error C2280: 'std::_Tree_node<...>::_Tree_node(void)':
  attempting to reference a deleted function
2025-12-03 08:13:44 +00:00
mutouyun
7eb7b93bfa Replace custom hash struct with std::hash in unordered_map definition 2025-12-03 08:13:44 +00:00
mutouyun
9d6bb92a15 Fix the issue caused by inconsistent lifecycle of the global IPC object. 2025-12-03 08:13:44 +00:00
mutouyun
a1f858f560 Refactoring the generic memory allocator 2025-12-03 08:13:44 +00:00
mutouyun
10c0d14de6 Reimplement the allocator required for the container type with $new 2025-12-03 08:13:44 +00:00
mutouyun
4e70d6c60b Use $new instead of alloc 2025-12-03 08:13:44 +00:00
mutouyun
5db7e70cdd Simplify the implementation of memory allocation management 2025-12-03 08:13:00 +00:00
mutouyun
8b384ba5f2 The memory allocator supports runtime dynamic size memory allocation 2025-12-03 08:13:00 +00:00
mutouyun
ef9d3b3642 libipc/memory/resource.h => libipc/mem/resource.h 2025-12-03 08:13:00 +00:00
mutouyun
e3c1755b9a Add $new 2025-12-03 08:11:57 +00:00
mutouyun
0b33859d33 Add block_pool 2025-12-03 08:11:57 +00:00
mutouyun
dd8307e81a Adjust the allocator name 2025-12-03 08:11:57 +00:00
mutouyun
823fda9ea1 Simplify verify_args function to fix error C3249 2025-12-03 08:11:57 +00:00
mutouyun
6fcb40d117 Fix fmt function to handle null pointers and return empty string 2025-12-03 08:11:57 +00:00
木头云
d0bd85a2c0 Update c-cpp.yml 2025-12-03 08:11:57 +00:00
mutouyun
a2da0621e8 Fix fmt function to handle empty strings and update make_prefix template parameters 2025-12-03 08:11:57 +00:00
mutouyun
e7a8005f58 Optimize memory_resource & add monotonic_buffer_resource 2025-12-03 08:11:57 +00:00
mutouyun
e83bd8f874 Add intrusive_stack 2025-12-03 08:11:57 +00:00
mutouyun
d260897b16 Add allocator and rewrite allocator_wrapper 2025-12-03 08:11:57 +00:00
mutouyun
976610f914 Optimized partial implementation using fmt 2025-12-03 08:11:57 +00:00
mutouyun
a4361d2b97 Start refactoring memory management, adding memory_resource 2025-12-03 08:11:57 +00:00
mutouyun
73ce564ab2 Update platform-specific feature macros to new interfaces in imp 2025-12-03 08:11:57 +00:00
mutouyun
b70dea04c5 Add system 2025-12-03 08:11:24 +00:00
mutouyun
453f964bdd Add result 2025-12-03 08:11:24 +00:00
mutouyun
09a304161f IPC_EXPORT => LIBIPC_EXPORT 2025-12-03 08:11:24 +00:00
mutouyun
1fd07a2dec Add log 2025-12-03 08:08:03 +00:00
mutouyun
4c1f829c06 Add error 2025-12-03 08:08:03 +00:00
mutouyun
e7b6fcd79d Added fmt support for byte 2025-12-03 08:08:03 +00:00
mutouyun
7e7bfb8467 Add fmt 2025-12-03 08:08:03 +00:00
mutouyun
e717ad46fb Add codecvt 2025-12-03 08:08:03 +00:00
mutouyun
7f73c24019 libimp => libipc 2025-12-03 08:08:03 +00:00
mutouyun
b3d520cd25 Add nameof & scope_exit 2025-12-03 08:08:03 +00:00
mutouyun
f987e35870 Move the export.h file to the imp directory 2025-12-03 08:08:03 +00:00
mutouyun
6387385fc0 Add expected 2025-12-03 08:08:03 +00:00
mutouyun
8098c8e37a Add imp for subsequent refactoring 2025-12-03 08:08:03 +00: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
木头云
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
126 changed files with 10458 additions and 1737 deletions

View File

@ -2,7 +2,7 @@ name: C/C++ CI
on:
push:
branches: [ master, develop, issue-* ]
branches: [ master, develop, issue-*, feature/* ]
pull_request:
branches: [ master, develop ]

View File

@ -1,6 +1,10 @@
/// \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>

View File

@ -1,7 +1,11 @@
/// \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>

View File

@ -5,26 +5,28 @@
#include <vector>
#include <type_traits>
#include "libipc/export.h"
#include "libipc/imp/export.h"
#include "libipc/def.h"
namespace ipc {
class IPC_EXPORT buffer {
class LIBIPC_EXPORT buffer {
public:
using destructor_t = void (*)(void*, std::size_t);
buffer();
buffer(void* p, std::size_t s, destructor_t d);
buffer(void* p, std::size_t s, destructor_t d, void* additional);
// 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);
template <std::size_t N>
explicit buffer(byte_t const (& data)[N])
explicit buffer(byte_t (& data)[N])
: buffer(data, sizeof(data)) {
}
explicit buffer(char const & c);
explicit buffer(char & c);
buffer(buffer&& rhs);
~buffer();
@ -57,8 +59,8 @@ public:
};
}
friend IPC_EXPORT bool operator==(buffer const & b1, buffer const & b2);
friend IPC_EXPORT bool operator!=(buffer const & b1, buffer const & b2);
friend LIBIPC_EXPORT bool operator==(buffer const & b1, buffer const & b2);
friend LIBIPC_EXPORT bool operator!=(buffer const & b1, buffer const & b2);
private:
class buffer_;

View File

@ -0,0 +1,66 @@
/**
* \file libconcur/intrusive_stack.h
* \author mutouyun (orz@orzz.org)
* \brief Define concurrent intrusive stack.
*/
#pragma once
#include <atomic>
namespace ipc {
namespace concur {
/// \brief Intrusive stack node.
/// \tparam T The type of the value.
template <typename T>
struct intrusive_node {
T value;
std::atomic<intrusive_node *> next;
};
/// \brief Intrusive stack.
/// \tparam T The type of the value.
/// \tparam Node The type of the node.
template <typename T, typename Node = intrusive_node<T>>
class intrusive_stack {
public:
using node = Node;
private:
std::atomic<node *> top_{nullptr};
public:
intrusive_stack(intrusive_stack const &) = delete;
intrusive_stack(intrusive_stack &&) = delete;
intrusive_stack &operator=(intrusive_stack const &) = delete;
intrusive_stack &operator=(intrusive_stack &&) = delete;
constexpr intrusive_stack() noexcept = default;
bool empty() const noexcept {
return top_.load(std::memory_order_acquire) == nullptr;
}
void push(node *n) noexcept {
node *old_top = top_.load(std::memory_order_acquire);
do {
n->next.store(old_top, std::memory_order_relaxed);
} while (!top_.compare_exchange_weak(old_top, n, std::memory_order_release
, std::memory_order_acquire));
}
node *pop() noexcept {
node *old_top = top_.load(std::memory_order_acquire);
do {
if (old_top == nullptr) {
return nullptr;
}
} while (!top_.compare_exchange_weak(old_top, old_top->next.load(std::memory_order_relaxed)
, std::memory_order_release
, std::memory_order_acquire));
return old_top;
}
};
} // namespace concur
} // namespace ipc

View File

@ -2,14 +2,14 @@
#include <cstdint> // std::uint64_t
#include "libipc/export.h"
#include "libipc/imp/export.h"
#include "libipc/def.h"
#include "libipc/mutex.h"
namespace ipc {
namespace sync {
class IPC_EXPORT condition {
class LIBIPC_EXPORT condition {
condition(condition const &) = delete;
condition &operator=(condition const &) = delete;

View File

@ -26,25 +26,26 @@ using uint_t = typename uint<N>::type;
// constants
enum : std::uint32_t {
invalid_value = (std::numeric_limits<std::uint32_t>::max)(),
default_timeout = 100, // ms
invalid_value = (std::numeric_limits<std::uint32_t>::max)(),
default_timeout = 100, // ms
};
enum : std::size_t {
data_length = 64,
large_msg_limit = data_length,
large_msg_align = 1024,
large_msg_cache = 32,
central_cache_default_size = 1024 * 1024, ///< 1MB
data_length = 64,
large_msg_limit = data_length,
large_msg_align = 1024,
large_msg_cache = 32,
};
enum class relat { // multiplicity of the relationship
single,
multi
single,
multi
};
enum class trans { // transmission
unicast,
broadcast
unicast,
broadcast
};
// producer-consumer policy flag
@ -57,9 +58,9 @@ struct relat_trait;
template <relat Rp, relat Rc, trans Ts>
struct relat_trait<wr<Rp, Rc, Ts>> {
constexpr static bool is_multi_producer = (Rp == relat::multi);
constexpr static bool is_multi_consumer = (Rc == relat::multi);
constexpr static bool is_broadcast = (Ts == trans::broadcast);
constexpr static bool is_multi_producer = (Rp == relat::multi);
constexpr static bool is_multi_consumer = (Rc == relat::multi);
constexpr static bool is_broadcast = (Ts == trans::broadcast);
};
template <template <typename> class Policy, typename Flag>
@ -67,7 +68,7 @@ struct relat_trait<Policy<Flag>> : relat_trait<Flag> {};
// the prefix tag of a channel
struct prefix {
char const *str;
char const *str;
};
} // namespace ipc

View File

@ -1,54 +0,0 @@
#pragma once
#if defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
# define IPC_DECL_EXPORT Q_DECL_EXPORT
# define IPC_DECL_IMPORT Q_DECL_IMPORT
#else // defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
/*
* Compiler & system detection for IPC_DECL_EXPORT & IPC_DECL_IMPORT.
* Not using QtCore cause it shouldn't depend on Qt.
*/
#if defined(_MSC_VER)
# define IPC_DECL_EXPORT __declspec(dllexport)
# define IPC_DECL_IMPORT __declspec(dllimport)
#elif defined(__ARMCC__) || defined(__CC_ARM)
# if defined(ANDROID) || defined(__linux__) || defined(__linux)
# define IPC_DECL_EXPORT __attribute__((visibility("default")))
# define IPC_DECL_IMPORT __attribute__((visibility("default")))
# else
# define IPC_DECL_EXPORT __declspec(dllexport)
# define IPC_DECL_IMPORT __declspec(dllimport)
# endif
#elif defined(__GNUC__)
# if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || \
defined(WIN64) || defined(_WIN64) || defined(__WIN64__)
# define IPC_DECL_EXPORT __declspec(dllexport)
# define IPC_DECL_IMPORT __declspec(dllimport)
# else
# define IPC_DECL_EXPORT __attribute__((visibility("default")))
# define IPC_DECL_IMPORT __attribute__((visibility("default")))
# endif
#else
# define IPC_DECL_EXPORT __attribute__((visibility("default")))
# define IPC_DECL_IMPORT __attribute__((visibility("default")))
#endif
#endif // defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
/*
* Define IPC_EXPORT for exporting function & class.
*/
#ifndef IPC_EXPORT
#if defined(LIBIPC_LIBRARY_SHARED_BUILDING__)
# define IPC_EXPORT IPC_DECL_EXPORT
#elif defined(LIBIPC_LIBRARY_SHARED_USING__)
# define IPC_EXPORT IPC_DECL_IMPORT
#else
# define IPC_EXPORT
#endif
#endif /*IPC_EXPORT*/

View File

@ -0,0 +1,74 @@
/**
* \file libipc/aligned.h
* \author mutouyun (orz@orzz.org)
* \brief Defines the type suitable for use as uninitialized storage for types of given type.
*/
#pragma once
#include <array>
#include <cstddef>
#include "libipc/imp/byte.h"
namespace ipc {
/**
* \brief The type suitable for use as uninitialized storage for types of given type.
* std::aligned_storage is deprecated in C++23, so we define our own.
* \tparam T The type to be aligned.
* \tparam AlignT The alignment of the type.
* \see https://en.cppreference.com/w/cpp/types/aligned_storage
* https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1413r3.pdf
*/
template <typename T, std::size_t AlignT = alignof(T)>
class aligned {
alignas(AlignT) std::array<ipc::byte, sizeof(T)> storage_;
public:
/**
* \brief Returns a pointer to the aligned storage.
* \return A pointer to the aligned storage.
*/
T *ptr() noexcept {
return reinterpret_cast<T *>(storage_.data());
}
/**
* \brief Returns a pointer to the aligned storage.
* \return A pointer to the aligned storage.
*/
T const *ptr() const noexcept {
return reinterpret_cast<const T *>(storage_.data());
}
/**
* \brief Returns a reference to the aligned storage.
* \return A reference to the aligned storage.
*/
T &ref() noexcept {
return *ptr();
}
/**
* \brief Returns a reference to the aligned storage.
* \return A reference to the aligned storage.
*/
T const &ref() const noexcept {
return *ptr();
}
};
/**
* \brief Rounds up the given value to the given alignment.
* \tparam T The type of the value.
* \param value The value to be rounded up.
* \param alignment The alignment to be rounded up to.
* \return The rounded up value.
* \see https://stackoverflow.com/questions/3407012/c-rounding-up-to-the-nearest-multiple-of-a-number
*/
template <typename T>
constexpr T round_up(T value, T alignment) noexcept {
return (value + alignment - 1) & ~(alignment - 1);
}
} // namespace ipc

178
include/libipc/imp/byte.h Normal file
View File

@ -0,0 +1,178 @@
/**
* \file libipc/byte.h
* \author mutouyun (orz@orzz.org)
* \brief Define the byte type.
*/
#pragma once
#include <type_traits>
#include <cstdint>
#include <cstddef> // std::byte (since C++17)
#include "libipc/imp/detect_plat.h"
#include "libipc/imp/span.h"
#include "libipc/imp/fmt.h"
#if defined(LIBIPC_CPP_17) && defined(__cpp_lib_byte)
#define LIBIPC_CPP_LIB_BYTE_
#endif // __cpp_lib_byte
namespace ipc {
class byte;
namespace detail_byte {
template <typename T>
using is_integral =
typename std::enable_if<std::is_integral<T>::value>::type;
template <typename T>
using is_not_byte =
typename std::enable_if<!std::is_same<
typename std::remove_cv<T>::type, byte>::value>::type;
} // namespace detail_byte
/**
* \brief A distinct type that implements the concept of byte as specified in the C++ language definition.
* \see https://en.cppreference.com/w/cpp/types/byte
*/
class byte {
std::uint8_t bits_;
public:
byte() noexcept = default;
template <typename T, typename = detail_byte::is_integral<T>>
constexpr byte(T v) noexcept
: bits_(static_cast<std::uint8_t>(v)) {}
#ifdef LIBIPC_CPP_LIB_BYTE_
constexpr byte(std::byte b) noexcept
: byte(std::to_integer<std::uint8_t>(b)) {}
#endif // LIBIPC_CPP_LIB_BYTE_
template <typename T, typename = detail_byte::is_integral<T>>
constexpr operator T() const noexcept {
return static_cast<T>(bits_);
}
#ifdef LIBIPC_CPP_LIB_BYTE_
constexpr operator std::byte() const noexcept {
/// \brief C++17 relaxed enum class initialization rules.
/// \see https://en.cppreference.com/w/cpp/language/enum#enum_relaxed_init_cpp17
return std::byte{bits_};
}
#endif // LIBIPC_CPP_LIB_BYTE_
friend bool operator==(byte const &lhs, byte const &rhs) noexcept {
return lhs.bits_ == rhs.bits_;
}
friend bool operator!=(byte const &lhs, byte const &rhs) noexcept {
return !(lhs == rhs);
}
};
/**
* \brief Non-member functions.
*/
template <typename T, typename = detail_byte::is_integral<T>>
constexpr T to_integer(byte b) noexcept {
return T(b);
}
/// \brief std::operator<<, operator>>
template <typename T, typename = detail_byte::is_integral<T>>
constexpr byte operator<<(byte b, T shift) noexcept {
return byte(to_integer<unsigned>(b) << shift);
}
template <typename T, typename = detail_byte::is_integral<T>>
constexpr byte operator>>(byte b, T shift) noexcept {
return byte(to_integer<unsigned>(b) >> shift);
}
/// \brief std::operator<<=, operator>>=
template <typename T, typename = detail_byte::is_integral<T>>
constexpr byte &operator<<=(byte &b, T shift) noexcept {
return b = b << shift;
}
template <typename T, typename = detail_byte::is_integral<T>>
constexpr byte &operator>>=(byte &b, T shift) noexcept {
return b = b >> shift;
}
/// \brief std::operator|, operator&, operator^, operator~
constexpr byte operator|(byte l, byte r) noexcept { return byte(to_integer<unsigned>(l) | to_integer<unsigned>(r)); }
constexpr byte operator&(byte l, byte r) noexcept { return byte(to_integer<unsigned>(l) & to_integer<unsigned>(r)); }
constexpr byte operator^(byte l, byte r) noexcept { return byte(to_integer<unsigned>(l) ^ to_integer<unsigned>(r)); }
constexpr byte operator~(byte b) noexcept { return byte(~to_integer<unsigned>(b)); }
/// \brief std::operator|=, operator&=, operator^=
constexpr byte &operator|=(byte &l, byte r) noexcept { return l = l | r; }
constexpr byte &operator&=(byte &l, byte r) noexcept { return l = l & r; }
constexpr byte &operator^=(byte &l, byte r) noexcept { return l = l ^ r; }
/// \brief Cast pointer to byte*.
template <typename T, typename = detail_byte::is_not_byte<T>>
byte *byte_cast(T *p) noexcept {
return reinterpret_cast<byte *>(p);
}
template <typename T, typename = detail_byte::is_not_byte<T>>
byte const *byte_cast(T const *p) noexcept {
return reinterpret_cast<byte const *>(p);
}
/// \brief Cast byte* to a pointer of another type.
template <typename T, typename = detail_byte::is_not_byte<T>>
T *byte_cast(byte *p) noexcept {
if (reinterpret_cast<std::size_t>(p) % alignof(T) != 0) {
return nullptr;
}
return reinterpret_cast<T *>(p);
}
template <typename T, typename U = typename std::add_const<T>::type,
typename = detail_byte::is_not_byte<T>>
U *byte_cast(byte const *p) noexcept {
if (reinterpret_cast<std::size_t>(p) % alignof(T) != 0) {
return nullptr;
}
return reinterpret_cast<U *>(p);
}
/// \brief Converts a span into a view of its underlying bytes.
/// \see https://en.cppreference.com/w/cpp/container/span/as_bytes
template <typename T,
typename Byte = typename std::conditional<std::is_const<T>::value, byte const, byte>::type>
auto as_bytes(span<T> s) noexcept -> span<Byte> {
return {byte_cast(s.data()), s.size_bytes()};
}
/// \brief Custom defined fmt_to method for imp::fmt
namespace detail_tag_invoke {
inline bool tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, ipc::byte b) {
return ipc::to_string(ctx, static_cast<std::uint8_t>(b), "02x");
}
template <typename T,
typename = std::enable_if_t<std::is_same<std::decay_t<T>, ipc::byte>::value>>
bool tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, fmt_ref<T> arg) noexcept {
return ipc::to_string(ctx, static_cast<std::uint8_t>(arg.param), arg.fstr);
}
} // namespace detail_tag_invoke
} // namespace ipc

View File

@ -0,0 +1,36 @@
/**
* \file libipc/codecvt.h
* \author mutouyun (orz@orzz.org)
* \brief Character set conversion interface.
*/
#pragma once
#include <string>
#include <cstddef>
#include "libipc/imp/export.h"
namespace ipc {
/**
* \brief The transform between UTF-8/16/32
*
* \param des The target string pointer can be nullptr
* \param dlen The target string length can be 0
*/
template <typename CharT, typename CharU>
LIBIPC_EXPORT std::size_t cvt_cstr(CharT const *src, std::size_t slen, CharU *des, std::size_t dlen) noexcept;
template <typename CharT, typename TraitsT, typename AllocT,
typename CharU, typename TraitsU, typename AllocU>
void cvt_sstr(std::basic_string<CharT, TraitsT, AllocT> const &src, std::basic_string<CharU, TraitsU, AllocU> &des) {
std::size_t dlen = cvt_cstr(src.c_str(), src.size(), (CharU *)nullptr, 0);
if (dlen == 0) {
des.clear();
return;
}
des.resize(dlen);
cvt_cstr(src.c_str(), src.size(), &des[0], des.size());
}
} // namespace ipc

View File

@ -0,0 +1,226 @@
/**
* \file libipc/detect_plat.h
* \author mutouyun (orz@orzz.org)
* \brief Define platform detection related interfaces.
*/
#pragma once
/// \brief OS check.
#if defined(WINCE) || defined(_WIN32_WCE)
# define LIBIPC_OS_WINCE
#elif defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || \
(defined(__x86_64) && defined(__MSYS__))
#define LIBIPC_OS_WIN64
#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || \
defined(__NT__) || defined(__MSYS__)
# define LIBIPC_OS_WIN32
#elif defined(__QNX__) || defined(__QNXNTO__)
# define LIBIPC_OS_QNX
#elif defined(__APPLE__)
# define LIBIPC_OS_APPLE
#elif defined(ANDROID) || defined(__ANDROID__)
# define LIBIPC_OS_ANDROID
#elif defined(__linux__) || defined(__linux)
# define LIBIPC_OS_LINUX
#elif defined(_POSIX_VERSION)
# define LIBIPC_OS_POSIX
#else
# error "This OS is unsupported."
#endif
#if defined(LIBIPC_OS_WIN32) || defined(LIBIPC_OS_WIN64) || \
defined(LIBIPC_OS_WINCE)
# define LIBIPC_OS_WIN
#endif
/// \brief Compiler check.
#if defined(_MSC_VER)
# define LIBIPC_CC_MSVC _MSC_VER
# define LIBIPC_CC_MSVC_2015 1900
# define LIBIPC_CC_MSVC_2017 1910
# define LIBIPC_CC_MSVC_2019 1920
# define LIBIPC_CC_MSVC_2022 1930
#elif defined(__GNUC__)
# define LIBIPC_CC_GNUC __GNUC__
# if defined(__clang__)
# define LIBIPC_CC_CLANG
#endif
#else
# error "This compiler is unsupported."
#endif
/// \brief Instruction set.
/// \see https://sourceforge.net/p/predef/wiki/Architectures/
#if defined(_M_X64) || defined(_M_AMD64) || \
defined(__x86_64__) || defined(__x86_64) || \
defined(__amd64__) || defined(__amd64)
# define LIBIPC_INSTR_X64
#elif defined(_M_IA64) || defined(__IA64__) || defined(_IA64) || \
defined(__ia64__) || defined(__ia64)
# define LIBIPC_INSTR_I64
#elif defined(_M_IX86) || defined(_X86_) || defined(__i386__) || defined(__i386)
# define LIBIPC_INSTR_X86
#elif defined(_M_ARM64) || defined(__arm64__) || defined(__aarch64__)
# define LIBIPC_INSTR_ARM64
#elif defined(_M_ARM) || defined(_ARM) || defined(__arm__) || defined(__arm)
# define LIBIPC_INSTR_ARM32
#else
# error "This instruction set is unsupported."
#endif
#if defined(LIBIPC_INSTR_X86) || defined(LIBIPC_INSTR_X64)
# define LIBIPC_INSTR_X86_64
#elif defined(LIBIPC_INSTR_ARM32) || defined(LIBIPC_INSTR_ARM64)
# define LIBIPC_INSTR_ARM
#endif
/// \brief Byte order.
#if defined(__BYTE_ORDER__)
# define LIBIPC_ENDIAN_BIG (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
# define LIBIPC_ENDIAN_LIT (!LIBIPC_ENDIAN_BIG)
#else
# define LIBIPC_ENDIAN_BIG (0)
# define LIBIPC_ENDIAN_LIT (1)
#endif
/// \brief C++ version.
#if (__cplusplus >= 202002L) && !defined(LIBIPC_CPP_20)
# define LIBIPC_CPP_20
#endif
#if (__cplusplus >= 201703L) && !defined(LIBIPC_CPP_17)
# define LIBIPC_CPP_17
#endif
#if /*(__cplusplus >= 201402L) &&*/ !defined(LIBIPC_CPP_14)
# define LIBIPC_CPP_14
#endif
#if !defined(LIBIPC_CPP_20) && \
!defined(LIBIPC_CPP_17) && \
!defined(LIBIPC_CPP_14)
# error "This C++ version is unsupported."
#endif
/// \brief Feature cross-platform adaptation.
#if defined(LIBIPC_CPP_17)
# define LIBIPC_INLINE_CONSTEXPR inline constexpr
#else
# define LIBIPC_INLINE_CONSTEXPR constexpr
#endif
/// \brief C++ attributes.
/// \see https://en.cppreference.com/w/cpp/language/attributes
#if defined(__has_cpp_attribute)
# if __has_cpp_attribute(fallthrough)
# define LIBIPC_FALLTHROUGH [[fallthrough]]
# endif
# if __has_cpp_attribute(maybe_unused)
# define LIBIPC_UNUSED [[maybe_unused]]
# endif
# if __has_cpp_attribute(likely)
# define LIBIPC_LIKELY(...) (__VA_ARGS__) [[likely]]
# endif
# if __has_cpp_attribute(unlikely)
# define LIBIPC_UNLIKELY(...) (__VA_ARGS__) [[unlikely]]
# endif
# if __has_cpp_attribute(nodiscard)
# define LIBIPC_NODISCARD [[nodiscard]]
# endif
# if __has_cpp_attribute(assume)
# define LIBIPC_ASSUME(...) [[assume(__VA_ARGS__)]]
# endif
#endif
#if !defined(LIBIPC_FALLTHROUGH)
# if defined(LIBIPC_CC_GNUC)
# define LIBIPC_FALLTHROUGH __attribute__((__fallthrough__))
# else
# define LIBIPC_FALLTHROUGH
# endif
#endif
#if !defined(LIBIPC_UNUSED)
# if defined(LIBIPC_CC_GNUC)
# define LIBIPC_UNUSED __attribute__((__unused__))
# elif defined(LIBIPC_CC_MSVC)
# define LIBIPC_UNUSED __pragma(warning(suppress: 4100 4101 4189))
# else
# define LIBIPC_UNUSED
# endif
#endif
#if !defined(LIBIPC_LIKELY)
# if defined(__has_builtin)
# if __has_builtin(__builtin_expect)
# define LIBIPC_LIKELY(...) (__builtin_expect(!!(__VA_ARGS__), 1))
# endif
# endif
#endif
#if !defined(LIBIPC_LIKELY)
# define LIBIPC_LIKELY(...) (__VA_ARGS__)
#endif
#if !defined(LIBIPC_UNLIKELY)
# if defined(__has_builtin)
# if __has_builtin(__builtin_expect)
# define LIBIPC_UNLIKELY(...) (__builtin_expect(!!(__VA_ARGS__), 0))
# endif
# endif
#endif
#if !defined(LIBIPC_UNLIKELY)
# define LIBIPC_UNLIKELY(...) (__VA_ARGS__)
#endif
#if !defined(LIBIPC_NODISCARD)
/// \see https://stackoverflow.com/questions/4226308/msvc-equivalent-of-attribute-warn-unused-result
# if defined(LIBIPC_CC_GNUC) && (LIBIPC_CC_GNUC >= 4)
# define LIBIPC_NODISCARD __attribute__((warn_unused_result))
# elif defined(LIBIPC_CC_MSVC) && (LIBIPC_CC_MSVC >= 1700)
# define LIBIPC_NODISCARD _Check_return_
# else
# define LIBIPC_NODISCARD
# endif
#endif
#if !defined(LIBIPC_ASSUME)
# if defined(__has_builtin)
# if __has_builtin(__builtin_assume)
/// \see https://clang.llvm.org/docs/LanguageExtensions.html#langext-builtin-assume
# define LIBIPC_ASSUME(...) __builtin_assume(__VA_ARGS__)
# elif __has_builtin(__builtin_unreachable)
/// \see https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-_005f_005fbuiltin_005funreachable
# define LIBIPC_ASSUME(...) do { if (!(__VA_ARGS__)) __builtin_unreachable(); } while (false)
# endif
# endif
#endif
#if !defined(LIBIPC_ASSUME)
# if defined(LIBIPC_CC_MSVC)
/// \see https://learn.microsoft.com/en-us/cpp/intrinsics/assume?view=msvc-140
# define LIBIPC_ASSUME(...) __assume(__VA_ARGS__)
# else
# define LIBIPC_ASSUME(...)
# endif
#endif
/// \see https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_exceptions.html
/// https://learn.microsoft.com/en-us/cpp/preprocessor/predefined-macros
/// https://stackoverflow.com/questions/6487013/programmatically-determine-whether-exceptions-are-enabled
#if defined(__cpp_exceptions) && __cpp_exceptions || \
defined(__EXCEPTIONS) || defined(_CPPUNWIND)
# define LIBIPC_TRY try
# define LIBIPC_CATCH(...) catch (__VA_ARGS__)
# define LIBIPC_THROW($EXCEPTION, ...) throw $EXCEPTION
#else
# define LIBIPC_TRY if (true)
# define LIBIPC_CATCH(...) else if (false)
# define LIBIPC_THROW($EXCEPTION, ...) return __VA_ARGS__
#endif

View File

@ -0,0 +1,27 @@
/**
* \file libipc/error.h
* \author mutouyun (orz@orzz.org)
* \brief A platform-dependent error code.
*/
#pragma once
#include <system_error>
#include <string>
#include <cstdint>
#include "libipc/imp/export.h"
#include "libipc/imp/fmt_cpo.h"
namespace ipc {
/**
* \brief Custom defined fmt_to method for imp::fmt
*/
namespace detail_tag_invoke {
inline bool tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, std::error_code const &ec) noexcept {
return fmt_to(ctx, '[', ec.value(), ": ", ec.message(), ']');
}
} // namespace detail_tag_invoke
} // namespace ipc

View File

@ -0,0 +1,392 @@
/**
* \file libipc/expected.h
* \author mutouyun (orz@orzz.org)
* \brief Provides a way to store either of two values.
*/
#pragma once
#include <type_traits>
#include <utility> // std::exchange
#include <array>
#include <memory> // std::addressof
#include <cstddef> // std::nullptr_t
#include "libipc/imp/uninitialized.h"
#include "libipc/imp/generic.h"
#include "libipc/imp/byte.h"
namespace ipc {
/**
* \brief In-place construction tag for unexpected value in expected.
* \see https://en.cppreference.com/w/cpp/utility/expected/unexpect_t
*/
struct unexpected_t {
explicit unexpected_t() = default;
};
constexpr unexpected_t unexpected{};
/**
* \class template <typename T, typename E> expected
* \brief Provides a way to store either of two values.
* \see https://en.cppreference.com/w/cpp/utility/expected
* \tparam T - the type of the expected value.
* \tparam E - the type of the unexpected value.
*/
template <typename T, typename E>
class expected;
namespace detail_expected {
template <typename T, typename E>
struct data_union {
using const_value_t = typename std::add_const<T>::type;
using const_error_t = typename std::add_const<E>::type;
union {
T value_; ///< the expected value
E error_; ///< the unexpected value
};
data_union(data_union const &) = delete;
data_union &operator=(data_union const &) = delete;
data_union(std::nullptr_t) noexcept {}
~data_union() {}
template <typename... A>
data_union(in_place_t, A &&...args) : value_{std::forward<A>(args)...} {}
template <typename... A>
data_union(unexpected_t, A &&...args) : error_{std::forward<A>(args)...} {}
void destruct_value() noexcept { destroy(&value_); }
void destruct_error() noexcept { destroy(&error_); }
const_value_t & value() const & noexcept { return value_; }
T & value() & noexcept { return value_; }
const_value_t &&value() const && noexcept { return std::move(value_); }
T && value() && noexcept { return std::move(value_); }
const_error_t & error() const & noexcept { return error_; }
E & error() & noexcept { return error_; }
const_error_t &&error() const && noexcept { return std::move(error_); }
E && error() && noexcept { return std::move(error_); }
};
template <typename E>
struct data_union<void, E> {
using const_error_t = typename std::add_const<E>::type;
alignas(E) std::array<byte, sizeof(E)> error_; ///< the unexpected value
data_union(data_union const &) = delete;
data_union &operator=(data_union const &) = delete;
data_union(std::nullptr_t) noexcept {}
template <typename... A>
data_union(in_place_t, A &&...) noexcept {}
template <typename... A>
data_union(unexpected_t, A &&...args) {
construct<E>(&error_, std::forward<A>(args)...);
}
void destruct_value() noexcept {}
void destruct_error() noexcept { destroy(reinterpret_cast<E *>(&error_)); }
const_error_t & error() const & noexcept { return *reinterpret_cast<E *>(error_.data()); }
E & error() & noexcept { return *reinterpret_cast<E *>(error_.data()); }
const_error_t &&error() const && noexcept { return std::move(*reinterpret_cast<E *>(error_.data())); }
E && error() && noexcept { return std::move(*reinterpret_cast<E *>(error_.data())); }
};
template <typename T, typename E>
auto destruct(bool /*has_value*/, data_union<T, E> &/*data*/) noexcept
-> typename std::enable_if<std::is_trivially_destructible<T>::value &&
std::is_trivially_destructible<E>::value>::type {
// Do nothing.
}
template <typename T, typename E>
auto destruct(bool has_value, data_union<T, E> &data) noexcept
-> typename std::enable_if<!std::is_trivially_destructible<T>::value &&
std::is_trivially_destructible<E>::value>::type {
if (has_value) data.destruct_value();
}
template <typename T, typename E>
auto destruct(bool has_value, data_union<T, E> &data) noexcept
-> typename std::enable_if< std::is_trivially_destructible<T>::value &&
!std::is_trivially_destructible<E>::value>::type {
if (!has_value) data.destruct_error();
}
template <typename T, typename E>
auto destruct(bool has_value, data_union<T, E> &data) noexcept
-> typename std::enable_if<!std::is_trivially_destructible<T>::value &&
!std::is_trivially_destructible<E>::value>::type {
if (has_value) {
data.destruct_value();
} else {
data.destruct_error();
}
}
template <typename S, typename T, typename E>
struct value_getter : data_union<T, E> {
using data_union<T, E>::data_union;
template <typename U>
value_getter(U &&other) : data_union<T, E>(nullptr) {
if (other) {
construct<data_union<T, E>>(this, in_place, std::forward<U>(other).value());
} else {
construct<data_union<T, E>>(this, unexpected, std::forward<U>(other).error());
}
}
T const *operator->() const noexcept { return std::addressof(this->value()); }
T * operator->() noexcept { return std::addressof(this->value()); }
T const & operator*() const & noexcept { return this->value(); }
T & operator*() & noexcept { return this->value(); }
T const &&operator*() const && noexcept { return std::move(this->value()); }
T && operator*() && noexcept { return std::move(this->value()); }
template <typename U>
T value_or(U &&def) const & {
return bool(*static_cast<S *>(this)) ? **this : static_cast<T>(std::forward<U>(def));
}
template <typename U>
T value_or(U &&def) && {
return bool(*static_cast<S *>(this)) ? std::move(**this) : static_cast<T>(std::forward<U>(def));
}
template <typename... A>
T &emplace(A &&...args) {
static_cast<S *>(this)->reconstruct(in_place, std::forward<A>(args)...);
return this->value();
}
void swap(S &other) {
if (bool(*static_cast<S *>(this)) && bool(other)) {
std::swap(this->value(), other.value());
} else if (!*static_cast<S *>(this) && !other) {
std::swap(this->error(), other.error());
} else if (!*static_cast<S *>(this) && bool(other)) {
E err(std::move(this->error()));
this->emplace(std::move(other.value()));
other.reconstruct(unexpected, std::move(err));
} else /*if (bool(*this) && !other)*/ {
E err(std::move(other.error()));
other.emplace(std::move(this->value()));
static_cast<S *>(this)->reconstruct(unexpected, std::move(err));
}
}
};
template <typename S, typename E>
struct value_getter<S, void, E> : data_union<void, E> {
using data_union<void, E>::data_union;
template <typename U>
value_getter(U &&other) : data_union<void, E>(nullptr) {
if (other) {
construct<data_union<void, E>>(this, in_place);
} else {
construct<data_union<void, E>>(this, unexpected, std::forward<U>(other).error());
}
}
void emplace() noexcept {
static_cast<S *>(this)->reconstruct(in_place);
}
void swap(S &other) {
if (bool(*static_cast<S *>(this)) && bool(other)) {
return;
} else if (!*static_cast<S *>(this) && !other) {
std::swap(this->error(), other.error());
} else if (!*static_cast<S *>(this) && bool(other)) {
E err(std::move(this->error()));
this->emplace();
other.reconstruct(unexpected, std::move(err));
} else /*if (bool(*this) && !other)*/ {
E err(std::move(other.error()));
other.emplace();
static_cast<S *>(this)->reconstruct(unexpected, std::move(err));
}
}
};
/**
* \brief Define the expected storage.
*/
template <typename T, typename E>
struct storage : value_getter<storage<T, E>, T, E> {
using getter_t = value_getter<storage<T, E>, T, E>;
bool has_value_;
template <typename... A>
storage(in_place_t, A &&...args)
: getter_t(in_place, std::forward<A>(args)...)
, has_value_(true) {}
template <typename... A>
storage(unexpected_t, A &&...args)
: getter_t(unexpected, std::forward<A>(args)...)
, has_value_(false) {}
storage(storage const &other)
: getter_t(other)
, has_value_(other.has_value_) {}
storage(storage &&other)
: getter_t(std::move(other))
/// After construction, has_value() is equal to other.has_value().
, has_value_(other.has_value_) {}
template <typename T_, typename E_>
storage(storage<T_, E_> const &other)
: getter_t(other)
, has_value_(other.has_value_) {}
template <typename T_, typename E_>
storage(storage<T_, E_> &&other)
: getter_t(std::move(other))
/// After construction, has_value() is equal to other.has_value().
, has_value_(other.has_value_) {}
bool has_value() const noexcept {
return has_value_;
}
explicit operator bool() const noexcept {
return this->has_value();
}
protected:
friend getter_t;
template <typename... A>
void reconstruct(A &&...args) {
destroy(this);
construct<storage>(this, std::forward<A>(args)...);
}
};
/// \brief The invoke forwarding helper.
template <typename F, typename... A>
auto invoke(F &&f, A &&...args) noexcept(
noexcept(std::forward<F>(f)(std::forward<A>(args)...)))
-> decltype(std::forward<F>(f)(std::forward<A>(args)...)) {
return std::forward<F>(f)(std::forward<A>(args)...);
}
template <typename F, typename... A>
auto invoke(F &&f, A &&...args) noexcept(
noexcept(std::forward<F>(f)()))
-> decltype(std::forward<F>(f)()) {
return std::forward<F>(f)();
}
/// \brief and_then helper.
template <typename E, typename F,
typename R = decltype(invoke(std::declval<F>(), *std::declval<E>()))>
R and_then(E &&exp, F &&f) {
static_assert(is_specialized<expected, R>::value, "F must return an `expected`.");
return bool(exp) ? invoke(std::forward<F>(f), *std::forward<E>(exp))
: R(unexpected, std::forward<E>(exp).error());
}
/// \brief or_else helper.
template <typename E, typename F,
typename R = decltype(invoke(std::declval<F>(), std::declval<E>().error()))>
R or_else(E &&exp, F &&f) {
static_assert(is_specialized<expected, R>::value, "F must return an `expected`.");
return bool(exp) ? std::forward<E>(exp)
: invoke(std::forward<F>(f), std::forward<E>(exp).error());
}
} // namespace detail_expected
/**
* \class template <typename T, typename E> expected
* \brief Provides a way to store either of two values.
*/
template <typename T, typename E>
class expected : public detail_expected::storage<typename std::remove_cv<T>::type, E> {
public:
using value_type = typename std::remove_cv<T>::type;
using error_type = E;
using detail_expected::storage<value_type, E>::storage;
expected(expected const &) = default;
expected(expected &&) = default;
expected()
: detail_expected::storage<value_type, E>(in_place) {}
expected &operator=(expected other) {
this->swap(other);
return *this;
}
// Monadic operations
template <typename F>
auto and_then(F &&f) & {
return detail_expected::and_then(*this, std::forward<F>(f));
}
template <typename F>
auto and_then(F &&f) const & {
return detail_expected::and_then(*this, std::forward<F>(f));
}
template <typename F>
auto and_then(F &&f) && {
return detail_expected::and_then(std::move(*this), std::forward<F>(f));
}
template <typename F>
auto or_else(F &&f) & {
return detail_expected::or_else(*this, std::forward<F>(f));
}
template <typename F>
auto or_else(F &&f) const & {
return detail_expected::or_else(*this, std::forward<F>(f));
}
template <typename F>
auto or_else(F &&f) && {
return detail_expected::or_else(std::move(*this), std::forward<F>(f));
}
};
// Compares
template <typename T1, typename E1, typename T2, typename E2>
bool operator==(expected<T1, E1> const &lhs, expected<T2, E2> const &rhs) {
return (lhs.has_value() == rhs.has_value())
&& (lhs.has_value() ? *lhs == *rhs : lhs.error() == rhs.error());
}
template <typename E1, typename E2>
bool operator==(expected<void, E1> const &lhs, expected<void, E2> const &rhs) {
return (lhs.has_value() == rhs.has_value())
&& (lhs.has_value() || lhs.error() == rhs.error());
}
template <typename T1, typename E1, typename T2, typename E2>
bool operator!=(expected<T1, E1> const &lhs, expected<T2, E2> const &rhs) {
return !(lhs == rhs);
}
} // namespace ipc

45
include/libipc/imp/export.h Executable file
View File

@ -0,0 +1,45 @@
/**
* \file libipc/export.h
* \author mutouyun (orz@orzz.org)
* \brief Define the symbol export interfaces.
*/
#pragma once
#include "libipc/imp/detect_plat.h"
#if defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
# define LIBIPC_DECL_EXPORT Q_DECL_EXPORT
# define LIBIPC_DECL_IMPORT Q_DECL_IMPORT
#else // defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
/**
* \brief Compiler & system detection for LIBIPC_DECL_EXPORT & LIBIPC_DECL_IMPORT.
* Not using QtCore cause it shouldn't depend on Qt.
*/
# if defined(LIBIPC_CC_MSVC) || defined(LIBIPC_OS_WIN)
# define LIBIPC_DECL_EXPORT __declspec(dllexport)
# define LIBIPC_DECL_IMPORT __declspec(dllimport)
# elif defined(LIBIPC_OS_ANDROID) || defined(LIBIPC_OS_LINUX) || defined(LIBIPC_CC_GNUC)
# define LIBIPC_DECL_EXPORT __attribute__((visibility("default")))
# define LIBIPC_DECL_IMPORT __attribute__((visibility("default")))
# else
# define LIBIPC_DECL_EXPORT __attribute__((visibility("default")))
# define LIBIPC_DECL_IMPORT __attribute__((visibility("default")))
# endif
#endif // defined(Q_DECL_EXPORT) && defined(Q_DECL_IMPORT)
/**
* \brief Define LIBIPC_EXPORT for exporting function & class.
*/
#ifndef LIBIPC_EXPORT
# if defined(LIBIPC_LIBRARY_SHARED_BUILDING__)
# define LIBIPC_EXPORT LIBIPC_DECL_EXPORT
# elif defined(LIBIPC_LIBRARY_SHARED_USING__)
# define LIBIPC_EXPORT LIBIPC_DECL_IMPORT
# else
# define LIBIPC_EXPORT
# endif
#endif /*LIBIPC_EXPORT*/

177
include/libipc/imp/fmt.h Normal file
View File

@ -0,0 +1,177 @@
/**
* \file libipc/fmt.h
* \author mutouyun (orz@orzz.org)
* \brief String formatting.
*
* \remarks The current performance is not high,
* because I use std::sprintf directly for formatting for convenience.
*/
#pragma once
#include <string>
#include <utility>
#include <type_traits>
#include <chrono> // std::chrono::time_point
#include <tuple>
#include <cstddef>
#include <ctime> // std::tm, std::localtime
#include "libipc/imp/fmt_cpo.h"
#include "libipc/imp/span.h"
#include "libipc/imp/detect_plat.h"
#include "libipc/imp/export.h"
namespace ipc {
/**
* \brief The format string reference wrapper.
*/
template <typename T>
struct fmt_ref {
span<char const> fstr;
T param;
};
/**
* \brief Conversion specifiers.
*
* \remarks Just like printf, the format string is of the form
* [flags][field_width][.precision][conversion_character]
*
* \see http://personal.ee.surrey.ac.uk/Personal/R.Bowden/C/printf.html
*/
template <std::size_t N>
auto spec(char const (&fstr)[N]) noexcept {
return [&fstr](auto &&arg) noexcept {
using arg_t = decltype(arg);
return fmt_ref<arg_t> {{fstr}, static_cast<arg_t>(arg)};
};
}
/**
* \brief String formatting function.
*
* \param args arguments that support the fmt output
* \return an empty string if the fmt output fails
*/
template <typename... A>
LIBIPC_NODISCARD std::string fmt(A &&...args) {
std::string joined;
fmt_context ctx(joined);
if (fmt_to(ctx, std::forward<A>(args)...)) {
return ctx.finish() ? joined : "";
}
return {};
}
/// \brief String types.
LIBIPC_EXPORT bool to_string(fmt_context &ctx, char const * a) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, std::string const &a) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, char const * a, span<char const> fstr) noexcept;
inline bool to_string(fmt_context &ctx, std::string const &a, span<char const> fstr) noexcept { return to_string(ctx, a.c_str(), fstr); }
/// \brief Character to string conversion.
LIBIPC_EXPORT bool to_string(fmt_context &ctx, char a) noexcept;
#if defined(LIBIPC_CPP_20)
inline bool to_string(fmt_context &ctx, char8_t a) noexcept { return to_string(ctx, (char)a); }
#endif // defined(LIBIPC_CPP_20)
LIBIPC_EXPORT bool to_string(fmt_context &ctx, wchar_t a) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, char16_t a) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, char32_t a) noexcept;
/// \brief Conversion of numeric types to strings.
LIBIPC_EXPORT bool to_string(fmt_context &ctx, signed short a, span<char const> fstr = {}) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, unsigned short a, span<char const> fstr = {}) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, signed int a, span<char const> fstr = {}) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, unsigned int a, span<char const> fstr = {}) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, signed long a, span<char const> fstr = {}) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, unsigned long a, span<char const> fstr = {}) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, signed long long a, span<char const> fstr = {}) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, unsigned long long a, span<char const> fstr = {}) noexcept;
inline bool to_string(fmt_context &ctx, signed char a, span<char const> fstr = {}) noexcept { return to_string(ctx, (int)a, fstr); }
inline bool to_string(fmt_context &ctx, unsigned char a, span<char const> fstr = {}) noexcept { return to_string(ctx, (unsigned)a, fstr); }
/// \brief Conversion of floating point type to strings.
LIBIPC_EXPORT bool to_string(fmt_context &ctx, double a, span<char const> fstr = {}) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, long double a, span<char const> fstr = {}) noexcept;
inline bool to_string(fmt_context &ctx, float a, span<char const> fstr = {}) noexcept { return to_string(ctx, (double)a, fstr); }
/// \brief Pointer.
LIBIPC_EXPORT bool to_string(fmt_context &ctx, std::nullptr_t) noexcept;
LIBIPC_EXPORT bool to_string(fmt_context &ctx, void const volatile *a) noexcept;
template <typename T,
typename = std::enable_if_t<!std::is_same<T, char>::value>>
inline bool to_string(fmt_context &ctx, T const volatile *a) noexcept { return to_string(ctx, (void *)a); }
/// \brief Date and time.
LIBIPC_EXPORT bool to_string(fmt_context &ctx, std::tm const &a, span<char const> fstr = {}) noexcept;
namespace detail_fmt {
/**
* \brief Convert std::time_t to std::string.
* \return an empty string if the conversion fails
*/
inline bool time_to_string(fmt_context &ctx, std::time_t tt, span<char const> fstr) noexcept {
#if defined(LIBIPC_CC_MSVC)
/// \see https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/localtime-s-localtime32-s-localtime64-s
std::tm tm{};
if (::localtime_s(&tm, &tt) != 0) {
return {};
}
return to_string(ctx, tm, fstr);
#else
return to_string(ctx, *std::localtime(&tt), fstr);
#endif
}
} // namespace detail_fmt
template <class Clock, class Duration>
bool to_string(fmt_context &ctx, std::chrono::time_point<Clock, Duration> const &a, span<char const> fstr = {}) noexcept {
return detail_fmt::time_to_string(ctx, std::chrono::system_clock::to_time_t(a), fstr);
}
/**
* \brief Predefined fmt_to method
*/
namespace detail_tag_invoke {
template <typename T>
auto tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, T &&arg) noexcept
-> decltype(ipc::to_string(ctx, std::forward<T>(arg))) {
return ipc::to_string(ctx, std::forward<T>(arg));
}
template <typename T>
auto tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, fmt_ref<T> arg) noexcept
-> decltype(ipc::to_string(ctx, static_cast<T>(arg.param), arg.fstr)) {
return ipc::to_string(ctx, static_cast<T>(arg.param), arg.fstr);
}
template <typename T>
bool tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, span<T> s) {
if (s.empty()) {
return false;
}
if (!fmt_to(ctx, s[0])) {
return false;
}
for (std::size_t i = 1; i < s.size(); ++i) {
if (!fmt_to(ctx, ' ', s[i])) return false;
}
return true;
}
template <typename Tp, std::size_t... I>
bool unfold_tuple_fmt_to(fmt_context &ctx, Tp const &tp, std::index_sequence<I...>) {
return fmt_to(ctx, std::get<I>(tp)...);
}
template <typename... T>
bool tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, std::tuple<T...> const &tp) {
return unfold_tuple_fmt_to(ctx, tp, std::index_sequence_for<T...>{});
}
} // namespace detail_tag_invoke
} // namespace ipc

View File

@ -0,0 +1,66 @@
/**
* \file libipc/fmt_cpo.h
* \author mutouyun (orz@orzz.org)
* \brief String formatting CPO.
*/
#pragma once
#include <string>
#include <cstddef>
#include <array>
#include "libipc/imp/generic.h"
#include "libipc/imp/detect_plat.h"
#include "libipc/imp/span.h"
#include "libipc/imp/export.h"
namespace ipc {
/**
* \class class LIBIPC_EXPORT fmt_context
* \brief The context of fmt.
*/
class LIBIPC_EXPORT fmt_context {
std::array<char, 2048U> sbuf_; ///< stack buffer
std::string &joined_;
std::size_t offset_;
public:
fmt_context(std::string &j) noexcept;
std::size_t capacity() noexcept;
void reset() noexcept;
bool finish() noexcept;
span<char> buffer(std::size_t sz) noexcept;
void expend(std::size_t sz) noexcept;
bool append(span<char const> const &str) noexcept;
};
/// \brief Supports custom fmt_to methods for ipc::fmt.
namespace detail_tag_invoke {
class fmt_to_t {
template <typename A1>
bool get_result(fmt_context &ctx, A1 && a1) const {
return ipc::tag_invoke(fmt_to_t{}, ctx, std::forward<A1>(a1));
}
template <typename A1, typename... A>
bool get_result(fmt_context &ctx, A1 && a1, A &&...args) const {
return get_result(ctx, std::forward<A1>(a1))
&& get_result(ctx, std::forward<A>(args)...);
}
public:
template <typename... A>
bool operator()(fmt_context &ctx, A &&...args) const {
return get_result(ctx, std::forward<A>(args)...);
}
};
} // namespace detail_tag_invoke
constexpr detail_tag_invoke::fmt_to_t fmt_to{};
} // namespace ipc

View File

@ -0,0 +1,301 @@
/**
* \file libipc/generic.h
* \author mutouyun (orz@orzz.org)
* \brief Tools for generic programming.
*/
#pragma once
#include <utility>
#include <type_traits> // std::declval, std::true_type, std::false_type
#include <cstddef> // std::size_t
#include "libipc/imp/detect_plat.h"
namespace ipc {
/**
* \brief Utility metafunction that maps a sequence of any types to the type void
* \see https://en.cppreference.com/w/cpp/types/void_t
*/
template <typename...>
using void_t = void;
/**
* \brief A type-list for generic programming.
*/
template <typename...>
struct types {};
/**
* \brief To indicate that the contained object should be constructed in-place.
* \see https://en.cppreference.com/w/cpp/utility/in_place
*/
#if defined(LIBIPC_CPP_17)
using std::in_place_t;
using std::in_place;
#else /*!LIBIPC_CPP_17*/
struct in_place_t {
explicit in_place_t() = default;
};
constexpr in_place_t in_place{};
#endif/*!LIBIPC_CPP_17*/
/**
* \brief A general pattern for supporting customisable functions
* \see https://www.open-std.org/jtc1/sc22/WG21/docs/papers/2019/p1895r0.pdf
*/
namespace detail_tag_invoke {
void tag_invoke();
struct tag_invoke_t {
template <typename T, typename... A>
constexpr auto operator()(T tag, A &&...args) const
noexcept(noexcept(tag_invoke(std::forward<T>(tag), std::forward<A>(args)...)))
-> decltype(tag_invoke(std::forward<T>(tag), std::forward<A>(args)...)) {
return tag_invoke(std::forward<T>(tag), std::forward<A>(args)...);
}
};
} // namespace detail_tag_invoke
constexpr detail_tag_invoke::tag_invoke_t tag_invoke{};
/**
* \brief Circumventing forwarding reference may override copy and move constructs.
* \see https://mpark.github.io/programming/2014/06/07/beware-of-perfect-forwarding-constructors/
*/
namespace detail_not_match {
template <typename T, typename... A>
struct is_same_first : std::false_type {};
template <typename T>
struct is_same_first<T, T> : std::true_type {};
} // namespace detail_not_match
template <typename T, typename... A>
using not_match =
typename std::enable_if<!detail_not_match::is_same_first<T,
typename std::decay<A>::type...>::value, bool>::type;
/**
* \brief Determines whether a type is specialized from a particular template.
*/
template <template <typename...> class Tt, typename T>
struct is_specialized : std::false_type {};
template <template <typename...> class Tt, typename... A>
struct is_specialized<Tt, Tt<A...>> : std::true_type {};
/**
* \brief Copy the cv qualifier and reference of the source type to the target type.
*/
template <typename Src, typename Des>
struct copy_cvref {
using type = Des;
};
template <typename Src, typename Des>
struct copy_cvref<Src const, Des> {
using type = typename std::add_const<Des>::type;
};
template <typename Src, typename Des>
struct copy_cvref<Src volatile, Des> {
using type = typename std::add_volatile<Des>::type;
};
template <typename Src, typename Des>
struct copy_cvref<Src const volatile, Des> {
using type = typename std::add_cv<Des>::type;
};
template <typename Src, typename Des>
struct copy_cvref<Src &, Des> {
using type = typename std::add_lvalue_reference<
typename copy_cvref<Src, Des>::type>::type;
};
template <typename Src, typename Des>
struct copy_cvref<Src &&, Des> {
using type = typename std::add_rvalue_reference<
typename copy_cvref<Src, Des>::type>::type;
};
template <typename Src, typename Des>
using copy_cvref_t = typename copy_cvref<Src, Des>::type;
/**
* \brief Returns the size of the given range.
* \see https://en.cppreference.com/w/cpp/iterator/size
*/
namespace detail_countof {
template <typename T>
struct trait_has_size {
private:
template <typename Type>
static std::true_type check(decltype(std::declval<Type>().size())*);
template <typename Type>
static std::false_type check(...);
public:
using type = decltype(check<T>(nullptr));
static constexpr auto value = type::value;
};
template <typename T>
struct trait_has_Size {
private:
template <typename Type>
static std::true_type check(decltype(std::declval<Type>().Size())*);
template <typename Type>
static std::false_type check(...);
public:
using type = decltype(check<T>(nullptr));
static constexpr auto value = type::value;
};
template <typename C, bool = trait_has_size<C>::value
, bool = trait_has_Size<C>::value>
struct trait;
template <typename T, std::size_t N>
struct trait<T[N], false, false> {
static constexpr auto countof(T const (&)[N]) noexcept {
return N;
}
};
template <typename C, bool B>
struct trait<C, true, B> {
static constexpr auto countof(C const &c) noexcept(noexcept(c.size())) {
return c.size();
}
};
template <typename C>
struct trait<C, false, true> {
static constexpr auto countof(C const &c) noexcept(noexcept(c.Size())) {
return c.Size();
}
};
} // namespace detail_countof
template <typename C,
typename T = detail_countof::trait<C>,
typename R = decltype(T::countof(std::declval<C const &>()))>
constexpr R countof(C const &c) noexcept(noexcept(T::countof(c))) {
return T::countof(c);
}
/**
* \brief Returns the data pointer of the given range.
* \see https://en.cppreference.com/w/cpp/iterator/data
*/
namespace detail_dataof {
template <typename T>
struct trait_has_data {
private:
template <typename Type>
static std::true_type check(decltype(std::declval<Type>().data())*);
template <typename Type>
static std::false_type check(...);
public:
using type = decltype(check<T>(nullptr));
static constexpr auto value = type::value;
};
template <typename T>
struct trait_has_Data {
private:
template <typename Type>
static std::true_type check(decltype(std::declval<Type>().Data())*);
template <typename Type>
static std::false_type check(...);
public:
using type = decltype(check<T>(nullptr));
static constexpr auto value = type::value;
};
template <typename C, bool = trait_has_data<C>::value
, bool = trait_has_Data<C>::value>
struct trait;
template <typename T, std::size_t N>
struct trait<T[N], false, false> {
static constexpr T const *dataof(T const (&arr)[N]) noexcept {
return arr;
}
static constexpr T *dataof(T (&arr)[N]) noexcept {
return arr;
}
};
template <typename C, bool B>
struct trait<C, true, B> {
template <typename T>
static constexpr auto dataof(T &&c) noexcept(noexcept(c.data())) {
return std::forward<T>(c).data();
}
};
template <typename C>
struct trait<C, false, true> {
template <typename T>
static constexpr auto dataof(T &&c) noexcept(noexcept(c.Data())) {
return std::forward<T>(c).Data();
}
};
template <typename C>
struct trait<C, false, false> {
template <typename T>
static constexpr T const *dataof(std::initializer_list<T> il) noexcept {
return il.begin();
}
template <typename T>
static constexpr T const *dataof(T const *p) noexcept {
return p;
}
};
} // namespace detail_dataof
template <typename C,
typename T = detail_dataof::trait<std::remove_cv_t<std::remove_reference_t<C>>>,
typename R = decltype(T::dataof(std::declval<C>()))>
constexpr R dataof(C &&c) noexcept(noexcept(T::dataof(std::forward<C>(c)))) {
return T::dataof(std::forward<C>(c));
}
/// \brief Returns after converting the value to the underlying type of E.
/// \see https://en.cppreference.com/w/cpp/types/underlying_type
/// https://en.cppreference.com/w/cpp/utility/to_underlying
template <typename E>
constexpr auto underlyof(E e) noexcept {
return static_cast<std::underlying_type_t<E>>(e);
}
namespace detail_horrible_cast {
template <typename T, typename U>
union temp {
U in;
T out;
};
} // namespace detail_horrible_cast
template <typename T, typename U>
constexpr auto horrible_cast(U &&in) noexcept
-> typename std::enable_if<std::is_trivially_copyable<T>::value
&& std::is_trivially_copyable<std::decay_t<U>>::value, T>::type {
return detail_horrible_cast::temp<T, std::decay_t<U>>{std::forward<U>(in)}.out;
}
} // namespace ipc

195
include/libipc/imp/log.h Normal file
View File

@ -0,0 +1,195 @@
/**
* \file libipc/log.h
* \author mutouyun (orz@orzz.org)
* \brief Simple log output component.
*/
#pragma once
#include <string>
#include <type_traits>
#include <chrono>
#include <tuple>
#include <utility>
#include <exception>
#include "libipc/imp/detect_plat.h"
#include "libipc/imp/export.h"
#include "libipc/imp/fmt.h"
#include "libipc/imp/generic.h"
namespace ipc {
namespace log {
enum class level : std::int32_t {
trace,
debug,
info,
warning,
error,
failed,
};
/// \struct template <typename... T> struct context
/// \brief Logging context.
/// \tparam ...T - a log records all parameter types passed
template <typename... T>
struct context {
log::level level;
std::chrono::system_clock::time_point tp;
char const *func;
std::tuple<T...> params;
};
/// \brief Custom defined fmt_to method for imp::fmt
namespace detail_log {
template <typename Tp, std::size_t... I, typename... A>
bool unfold_tuple_fmt_to(fmt_context &ctx, Tp const &tp, std::index_sequence<I...>, A &&...args) {
return fmt_to(ctx, std::forward<A>(args)..., std::get<I>(tp)...);
}
} // namespace detail_log
template <typename... T>
bool context_to_string(fmt_context &f_ctx, context<T...> const &l_ctx) {
static constexpr char types[] = {
'T', 'D', 'I', 'W', 'E', 'F',
};
auto ms = std::chrono::time_point_cast<std::chrono::milliseconds>(l_ctx.tp).time_since_epoch().count() % 1000;
return detail_log::unfold_tuple_fmt_to(f_ctx, l_ctx.params, std::index_sequence_for<T...>{},
"[", types[underlyof(l_ctx.level)], "]"
"[", l_ctx.tp, ".", spec("03")(ms), "]"
"[", l_ctx.func, "] ");
}
template <typename... T>
std::string context_to_string(context<T...> const &l_ctx) {
std::string log_txt;
fmt_context f_ctx(log_txt);
LIBIPC_TRY {
if (!context_to_string(f_ctx, l_ctx)) {
return {};
}
f_ctx.finish();
return log_txt;
} LIBIPC_CATCH(...) {
f_ctx.finish();
throw;
}
}
/// \brief Standard console output object.
inline auto &make_std_out() noexcept {
static auto std_out = [](auto const &ctx) {
auto s = context_to_string(ctx);
switch (ctx.level) {
case level::trace:
case level::debug:
case level::info:
std::fprintf(stdout, "%s\n", s.c_str());
break;
case level::warning:
case level::error:
case level::failed:
std::fprintf(stderr, "%s\n", s.c_str());
break;
default:
break;
}
};
return std_out;
}
/// \brief Get the exception information.
inline char const *exception_string(std::exception_ptr eptr) noexcept {
LIBIPC_TRY {
if (eptr) {
std::rethrow_exception(eptr);
}
} LIBIPC_CATCH(std::exception const &e) {
return e.what();
} LIBIPC_CATCH(...) {
return "unknown";
}
return "none";
}
/// \brief Record the last information when an exception occurs.
inline void exception_print(char const *func, std::exception_ptr eptr) noexcept {
if (func == nullptr) {
func = "-";
}
std::fprintf(stderr, "[F][%s] exception: %s\n", func, exception_string(eptr));
}
/**
* \brief Log information base class.
*/
class logger_base {
protected:
char const *func_;
level level_limit_;
logger_base(char const *func, level level_limit) noexcept
: func_ (func)
, level_limit_(level_limit) {}
};
/**
* \brief Log information grips.
*/
template <typename Outputer>
class logger : public logger_base {
Outputer out_;
public:
template <typename O>
logger(char const *func, O &&out, level level_limit) noexcept
: logger_base(func, level_limit)
, out_ (std::forward<O>(out)) {}
template <typename... A>
logger const &operator()(log::level l, A &&...args) const noexcept {
if (underlyof(l) < underlyof(level_limit_)) {
return *this;
}
LIBIPC_TRY {
out_(context<A &&...> {
l, std::chrono::system_clock::now(), func_,
std::forward_as_tuple(std::forward<A>(args)...),
});
} LIBIPC_CATCH(...) {
exception_print(func_, std::current_exception());
}
return *this;
}
template <typename... A> logger const &trace (A &&...args) const noexcept { return (*this)(log::level::trace , std::forward<A>(args)...); }
template <typename... A> logger const &debug (A &&...args) const noexcept { return (*this)(log::level::debug , std::forward<A>(args)...); }
template <typename... A> logger const &info (A &&...args) const noexcept { return (*this)(log::level::info , std::forward<A>(args)...); }
template <typename... A> logger const &warning(A &&...args) const noexcept { return (*this)(log::level::warning, std::forward<A>(args)...); }
template <typename... A> logger const &error (A &&...args) const noexcept { return (*this)(log::level::error , std::forward<A>(args)...); }
template <typename... A> logger const &failed (A &&...args) const noexcept { return (*this)(log::level::failed , std::forward<A>(args)...); }
};
template <typename O>
inline auto make_logger(char const *func, O &&out, level level_limit = level::info) noexcept {
return logger<std::decay_t<O>>(func, std::forward<O>(out), level_limit);
}
inline auto make_logger(char const *func, level level_limit = level::info) noexcept {
return make_logger(func, make_std_out(), level_limit);
}
inline auto make_logger(char const * /*ignore*/, char const *name, level level_limit = level::info) noexcept {
return make_logger(name, make_std_out(), level_limit);
}
#define LIBIPC_LOG(...) \
auto log \
= [](auto &&...args) noexcept { \
return ::ipc::log::make_logger(__func__, std::forward<decltype(args)>(args)...); \
}(__VA_ARGS__)
} // namespace log
} // namespace ipc

View File

@ -0,0 +1,43 @@
/**
* \file libipc/nameof.h
* \author mutouyun (orz@orzz.org)
* \brief Gets the name string of a type.
*/
#pragma once
#include <typeinfo>
#include <string>
#include <cstring>
#include "libipc/imp/export.h"
#include "libipc/imp/span.h"
#include "libipc/imp/detect_plat.h"
namespace ipc {
/**
* \brief The conventional way to obtain demangled symbol name.
* \see https://www.boost.org/doc/libs/1_80_0/libs/core/doc/html/core/demangle.html
*
* \param name the mangled name
* \return std::string a human-readable demangled type name
*/
LIBIPC_EXPORT std::string demangle(std::string name) noexcept;
/**
* \brief Returns an implementation defined string containing the name of the type.
* \see https://en.cppreference.com/w/cpp/types/type_info/name
*
* \tparam T a type
* \return std::string a human-readable demangled type name
*/
template <typename T>
std::string nameof() noexcept {
LIBIPC_TRY {
return demangle(typeid(T).name());
} LIBIPC_CATCH(...) {
return {};
}
}
} // namespace ipc

138
include/libipc/imp/result.h Normal file
View File

@ -0,0 +1,138 @@
/**
* \file libipc/result.h
* \author mutouyun (orz@orzz.org)
* \brief Define the return value type with an error status code.
*/
#pragma once
#include <type_traits>
#include <utility>
#include <cstdint>
#include "libipc/imp/expected.h"
#include "libipc/imp/error.h"
#include "libipc/imp/generic.h"
#include "libipc/imp/fmt.h"
namespace ipc {
namespace detail_result {
template <typename T>
struct generic_initializer {
using storage_t = expected<T, std::error_code>;
/// \brief Custom initialization.
static constexpr storage_t init_code() noexcept {
return {unexpected, std::error_code(-1, std::generic_category())};
}
static constexpr storage_t init_code(T value) noexcept {
return {in_place, value};
}
static constexpr storage_t init_code(std::error_code const &ec) noexcept {
return {unexpected, ec};
}
};
template <typename T, typename = void>
struct result_base;
template <typename T>
struct result_base<T, std::enable_if_t<std::is_pointer<T>::value>>
: generic_initializer<T> {
using storage_t = typename generic_initializer<T>::storage_t;
using generic_initializer<T>::init_code;
static constexpr storage_t init_code(std::nullptr_t, std::error_code const &ec) noexcept {
return {unexpected, ec};
}
static constexpr storage_t init_code(std::nullptr_t) noexcept {
return {unexpected, std::error_code(-1, std::generic_category())};
}
};
template <typename T>
struct result_base<T, std::enable_if_t<std::is_integral<T>::value || std::is_enum<T>::value>>
: generic_initializer<T> {
using storage_t = typename generic_initializer<T>::storage_t;
using generic_initializer<T>::init_code;
};
} // namespace detail_result
/**
* \class class result
* \brief The generic wrapper for the result type.
*/
template <typename T>
class result : public detail_result::result_base<T> {
private:
using base_t = detail_result::result_base<T>;
using storage_t = typename base_t::storage_t;
storage_t ret_; ///< internal data
public:
template <typename... A,
typename = not_match<result, A...>,
typename = decltype(base_t::init_code(std::declval<A>()...))>
result(A &&...args) noexcept
: ret_(base_t::init_code(std::forward<A>(args)...)) {}
std::string format_string() const {
if LIBIPC_LIKELY(ret_) {
return fmt("value = ", ret_.value());
} else {
return fmt("error = ", ret_.error());
}
}
T value() const noexcept { return ret_ ? ret_.value() : T{}; }
bool ok () const noexcept { return ret_.has_value(); }
std::error_code error() const noexcept { return ret_.error(); }
T operator * () const noexcept { return value(); }
explicit operator bool() const noexcept { return ok (); }
friend bool operator==(result const &lhs, result const &rhs) noexcept { return lhs.ret_ == rhs.ret_; }
friend bool operator!=(result const &lhs, result const &rhs) noexcept { return !(lhs == rhs); }
};
template <>
class result<void> {
private:
std::error_code ret_; ///< internal data
public:
result() noexcept
: ret_(-1, std::generic_category()) {}
result(std::error_code const &ec) noexcept
: ret_(ec) {}
std::string format_string() const {
return fmt("error = ", error());
}
bool ok () const noexcept { return !ret_; }
std::error_code error() const noexcept { return ret_; }
explicit operator bool () const noexcept { return ok(); }
friend bool operator==(result const &lhs, result const &rhs) noexcept { return lhs.ret_ == rhs.ret_; }
friend bool operator!=(result const &lhs, result const &rhs) noexcept { return !(lhs == rhs); }
};
/// \brief Custom defined fmt_to method for imp::fmt
namespace detail_tag_invoke {
template <typename T>
inline bool tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, result<T> const &r) {
return fmt_to(ctx, (r ? "succ" : "fail"), ", ", r.format_string());
}
} // namespace detail_tag_invoke
} // namespace ipc

View File

@ -0,0 +1,77 @@
/**
* \file libipc/scope_exit.h
* \author mutouyun (orz@orzz.org)
* \brief Execute guard function when the enclosing scope exits.
*/
#pragma once
#include <utility> // std::forward, std::move
#include <functional> // std::function
#include <type_traits>
#include "libipc/imp/detect_plat.h"
namespace ipc {
template <typename F = std::function<void()>>
class scope_exit {
F destructor_;
mutable bool released_;
public:
template <typename G>
explicit scope_exit(G &&destructor) noexcept
: destructor_(std::forward<G>(destructor))
, released_ (false) {}
scope_exit(scope_exit &&other) noexcept
: destructor_(std::move(other.destructor_))
, released_ (std::exchange(other.released_, true)) /*release rhs*/ {}
scope_exit &operator=(scope_exit &&other) noexcept {
destructor_ = std::move(other.destructor_);
released_ = std::exchange(other.released_, true);
return *this;
}
~scope_exit() noexcept {
if (!released_) destructor_();
}
void release() const noexcept {
released_ = true;
}
void do_exit() noexcept {
if (released_) return;
destructor_();
released_ = true;
}
void swap(scope_exit &other) noexcept {
std::swap(destructor_, other.destructor_);
std::swap(released_ , other.released_);
}
};
/// \brief Creates a scope_exit object.
template <typename F>
auto make_scope_exit(F &&destructor) noexcept {
return scope_exit<std::decay_t<F>>(std::forward<F>(destructor));
}
namespace detail_scope_exit {
struct scope_exit_helper {
template <typename F>
auto operator=(F &&f) const noexcept {
return make_scope_exit(std::forward<F>(f));
}
};
} // namespace detail_scope_exit
#define LIBIPC_SCOPE_EXIT($VAL) \
LIBIPC_UNUSED auto $VAL = ::ipc::detail_scope_exit::scope_exit_helper{}
} // namespace ipc

281
include/libipc/imp/span.h Normal file
View File

@ -0,0 +1,281 @@
/**
* \file libipc/span.h
* \author mutouyun (orz@orzz.org)
* \brief Describes an object that can refer to a contiguous sequence of objects.
*/
#pragma once
#include <type_traits>
#include <cstddef>
#include <iterator>
#include <array>
#include <vector>
#include <string>
#include <limits>
#include <algorithm>
#include <cstdint>
#include <initializer_list>
#include "libipc/imp/detect_plat.h"
#include "libipc/imp/generic.h"
#if defined(LIBIPC_CPP_20) && defined(__cpp_lib_span)
#include <span>
#define LIBIPC_CPP_LIB_SPAN_
#endif // __cpp_lib_span
namespace ipc {
namespace detail_span {
/// \brief Helper trait for span.
template <typename From, typename To>
using array_convertible = std::is_convertible<From(*)[], To(*)[]>;
template <typename From, typename To>
using is_array_convertible =
typename std::enable_if<array_convertible<From, To>::value>::type;
template <typename T, typename Ref>
using compatible_ref = array_convertible<typename std::remove_reference<Ref>::type, T>;
template <typename It>
using iter_reference_t = decltype(*std::declval<It&>());
template <typename T, typename It>
using is_compatible_iter =
typename std::enable_if<compatible_ref<T, iter_reference_t<It>>::value>::type;
template <typename From, typename To>
using is_inconvertible =
typename std::enable_if<!std::is_convertible<From, To>::value>::type;
template <typename S, typename I>
using is_sized_sentinel_for =
typename std::enable_if<std::is_convertible<decltype(std::declval<S>() - std::declval<I>()),
std::ptrdiff_t>::value>::type;
template <typename T>
using is_continuous_container =
decltype(dataof(std::declval<T>()), countof(std::declval<T>()));
/// \brief Obtain the address represented by p
/// without forming a reference to the object pointed to by p.
/// \see https://en.cppreference.com/w/cpp/memory/to_address
template<typename T>
constexpr T *to_address(T *ptr) noexcept {
static_assert(!std::is_function<T>::value, "ptr shouldn't a function pointer");
return ptr;
}
template<typename T>
constexpr auto to_address(T const &ptr)
noexcept(noexcept(ptr.operator->()))
-> decltype(ptr.operator->()) {
return to_address(ptr.operator->());
}
} // namespace detail_span
/**
* \brief A simple implementation of span.
* \see https://en.cppreference.com/w/cpp/container/span
*/
template <typename T>
class span {
public:
using element_type = T;
using value_type = typename std::remove_cv<element_type>::type;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using pointer = element_type *;
using const_pointer = typename std::remove_const<element_type>::type const *;
using reference = element_type &;
using const_reference = typename std::remove_const<element_type>::type const &;
using iterator = pointer;
using reverse_iterator = std::reverse_iterator<iterator>;
private:
pointer ptr_ {nullptr};
size_type extent_ {0};
public:
constexpr span() noexcept = default;
constexpr span(span const &) noexcept = default;
#if (LIBIPC_CC_MSVC > LIBIPC_CC_MSVC_2015)
constexpr
#endif
span & operator=(span const &) noexcept = default;
template <typename It,
typename = detail_span::is_compatible_iter<T, It>>
constexpr span(It first, size_type count) noexcept
: ptr_ (detail_span::to_address(first))
, extent_(count) {}
template <typename It, typename End,
typename = detail_span::is_compatible_iter<T, It>,
typename = detail_span::is_sized_sentinel_for<End, It>,
typename = detail_span::is_inconvertible<End, size_type>>
constexpr span(It first, End last) noexcept(noexcept(last - first))
: ptr_ (detail_span::to_address(first))
, extent_(static_cast<size_type>(last - first)) {}
template <typename U,
typename = detail_span::is_continuous_container<U>,
typename = detail_span::is_array_convertible<std::remove_pointer_t<
decltype(dataof(std::declval<U>()))>, element_type>>
constexpr span(U &&u) noexcept(noexcept(dataof (std::forward<U>(u)),
countof(std::forward<U>(u))))
: ptr_ (dataof (std::forward<U>(u)))
, extent_(countof(std::forward<U>(u))) {}
template <typename U, std::size_t E,
typename = detail_span::is_array_convertible<U, element_type>>
constexpr span(U (&arr)[E]) noexcept
: span(static_cast<pointer>(arr), E) {}
template <typename U, std::size_t E,
typename = detail_span::is_array_convertible<U, element_type>>
constexpr span(std::array<U, E> &arr) noexcept
: span(static_cast<pointer>(arr.data()), E) {}
template <typename U, std::size_t E,
typename = detail_span::is_array_convertible<typename std::add_const<U>::type, element_type>>
constexpr span(std::array<U, E> const &arr) noexcept
: span(static_cast<pointer>(arr.data()), E) {}
template <typename U,
typename = detail_span::is_array_convertible<U, T>>
constexpr span(span<U> const &s) noexcept
: ptr_ (s.data())
, extent_(s.size()) {}
#ifdef LIBIPC_CPP_LIB_SPAN_
template <typename U, std::size_t E,
typename = detail_span::is_array_convertible<U, T>>
constexpr span(std::span<U, E> const &s) noexcept
: ptr_ (s.data())
, extent_(s.size()) {}
#endif // LIBIPC_CPP_LIB_SPAN_
constexpr size_type size() const noexcept {
return extent_;
}
constexpr size_type size_bytes() const noexcept {
return size() * sizeof(element_type);
}
constexpr bool empty() const noexcept {
return size() == 0;
}
constexpr pointer data() const noexcept {
return this->ptr_;
}
constexpr reference front() const noexcept {
return *data();
}
constexpr reference back() const noexcept {
return *(data() + (size() - 1));
}
constexpr reference operator[](size_type idx) const noexcept {
return *(data() + idx);
}
constexpr iterator begin() const noexcept {
return iterator(data());
}
constexpr iterator end() const noexcept {
return iterator(data() + this->size());
}
constexpr reverse_iterator rbegin() const noexcept {
return reverse_iterator(this->end());
}
constexpr reverse_iterator rend() const noexcept {
return reverse_iterator(this->begin());
}
constexpr span first(size_type count) const noexcept {
return span(begin(), count);
}
constexpr span last(size_type count) const noexcept {
return span(end() - count, count);
}
constexpr span subspan(size_type offset, size_type count = (std::numeric_limits<size_type>::max)()) const noexcept {
return (offset >= size()) ? span() : span(begin() + offset, (std::min)(size() - offset, count));
}
};
/// \brief Support for span equals comparison.
template <typename T, typename U,
typename = decltype(std::declval<T>() == std::declval<U>())>
bool operator==(span<T> a, span<U> b) noexcept {
if (a.size() != b.size()) {
return false;
}
for (std::size_t i = 0; i < a.size(); ++i) {
if (a[i] != b[i]) return false;
}
return true;
}
/// \brief Constructs an object of type T and wraps it in a span.
/// Before C++17, template argument deduction for class templates was not supported.
/// \see https://en.cppreference.com/w/cpp/language/template_argument_deduction
template <typename T>
auto make_span(T *arr, std::size_t count) noexcept -> span<T> {
return {arr, count};
}
template <typename T, std::size_t E>
auto make_span(T (&arr)[E]) noexcept -> span<T> {
return {arr};
}
template <typename T, std::size_t E>
auto make_span(std::array<T, E> &arr) noexcept -> span<T> {
return {arr};
}
template <typename T, std::size_t E>
auto make_span(std::array<T, E> const &arr) noexcept -> span<typename std::add_const<T>::type> {
return {arr};
}
template <typename T>
auto make_span(std::vector<T> &arr) noexcept -> span<T> {
return {arr.data(), arr.size()};
}
template <typename T>
auto make_span(std::vector<T> const &arr) noexcept -> span<typename std::add_const<T>::type> {
return {arr.data(), arr.size()};
}
template <typename T>
auto make_span(std::initializer_list<T> list) noexcept -> span<typename std::add_const<T>::type> {
return {list.begin(), list.end()};
}
inline auto make_span(std::string &str) noexcept -> span<char> {
return {const_cast<char *>(str.data()), str.size()};
}
inline auto make_span(std::string const &str) noexcept -> span<char const> {
return {str.data(), str.size()};
}
} // namespace ipc

View File

@ -0,0 +1,33 @@
/**
* \file libipc/system.h
* \author mutouyun (orz@orzz.org)
* \brief Isolation and encapsulation of system APIs.
*/
#pragma once
#include <string>
#include <ostream> // std::ostream
#include <cstdint>
#include "libipc/imp/export.h"
#include "libipc/imp/error.h"
#include "libipc/imp/result.h"
namespace ipc {
namespace sys {
/// \brief A platform-dependent error code.
LIBIPC_EXPORT std::error_code error() noexcept;
/// \enum The name of the `conf()` argument used to inquire about its value.
/// \brief Certain options are supported,
/// or what the value is of certain configurable constants or limits.
enum class info : std::int32_t {
page_size,
};
/// \brief Get system configuration information at run time.
LIBIPC_EXPORT result<std::int64_t> conf(info) noexcept;
} // namespace sys
} // namespace ipc

View File

@ -0,0 +1,163 @@
/**
* \file libipc/uninitialized.h
* \author mutouyun (orz@orzz.org)
* \brief Uninitialized memory algorithms.
*/
#pragma once
#include <new> // placement-new
#include <type_traits> // std::enable_if_t
#include <utility> // std::forward
#include <memory> // std::construct_at, std::destroy_at, std::addressof
#include <cstddef>
#include "libipc/imp/detect_plat.h"
#include "libipc/imp/generic.h"
namespace ipc {
/**
* \brief Creates an object at a given address, like 'construct_at' in c++20
* \see https://en.cppreference.com/w/cpp/memory/construct_at
*/
// Overload for zero arguments - use value initialization
template <typename T>
T* construct(void *p) {
#if defined(LIBIPC_CPP_20)
return std::construct_at(static_cast<T *>(p));
#else
return ::new (p) T();
#endif
}
// Overload for one or more arguments - prefer direct initialization
template <typename T, typename A1, typename... A>
auto construct(void *p, A1 &&arg1, A &&...args)
-> std::enable_if_t<::std::is_constructible<T, A1, A...>::value, T *> {
#if defined(LIBIPC_CPP_20)
return std::construct_at(static_cast<T *>(p), std::forward<A1>(arg1), std::forward<A>(args)...);
#else
return ::new (p) T(std::forward<A1>(arg1), std::forward<A>(args)...);
#endif
}
// Overload for non-constructible types - use aggregate initialization
template <typename T, typename A1, typename... A>
auto construct(void *p, A1 &&arg1, A &&...args)
-> std::enable_if_t<!::std::is_constructible<T, A1, A...>::value, T *> {
return ::new (p) T{std::forward<A1>(arg1), std::forward<A>(args)...};
}
/**
* \brief Destroys an object at a given address, like 'destroy_at' in c++17
* \see https://en.cppreference.com/w/cpp/memory/destroy_at
*/
template <typename T>
void *destroy(T *p) noexcept {
if (p == nullptr) return nullptr;
#if defined(LIBIPC_CPP_17)
std::destroy_at(p);
#else
p->~T();
#endif
return p;
}
template <>
inline void *destroy<void>(void *p) noexcept {
return p;
}
template <typename T, std::size_t N>
void *destroy(T (*p)[N]) noexcept {
if (p == nullptr) return nullptr;
#if defined(LIBIPC_CPP_20)
std::destroy_at(p);
#elif defined(LIBIPC_CPP_17)
std::destroy(std::begin(*p), std::end(*p));
#else
for (auto &elem : *p) destroy(std::addressof(elem));
#endif
return p;
}
/**
* \brief Destroys a range of objects.
* \see https://en.cppreference.com/w/cpp/memory/destroy
*/
template <typename ForwardIt>
void destroy(ForwardIt first, ForwardIt last) noexcept {
#if defined(LIBIPC_CPP_17)
std::destroy(first, last);
#else
for (; first != last; ++first) {
destroy(std::addressof(*first));
}
#endif
}
/**
* \brief Destroys a number of objects in a range.
* \see https://en.cppreference.com/w/cpp/memory/destroy_n
*/
template <typename ForwardIt, typename Size>
ForwardIt destroy_n(ForwardIt first, Size n) noexcept {
#if defined(LIBIPC_CPP_17)
return std::destroy_n(first, n);
#else
for (; n > 0; (void) ++first, --n)
destroy(std::addressof(*first));
return first;
#endif
}
/**
* \brief Constructs objects by default-initialization
* in an uninitialized area of memory, defined by a start and a count.
* \see https://en.cppreference.com/w/cpp/memory/uninitialized_default_construct_n
*/
template <typename ForwardIt, typename Size>
ForwardIt uninitialized_default_construct_n(ForwardIt first, Size n) {
#if defined(LIBIPC_CPP_17)
return std::uninitialized_default_construct_n(first, n);
#else
using T = typename std::iterator_traits<ForwardIt>::value_type;
ForwardIt current = first;
LIBIPC_TRY {
for (; n > 0; (void) ++current, --n)
::new (horrible_cast<void *>(std::addressof(*current))) T;
return current;
} LIBIPC_CATCH(...) {
destroy(first, current);
LIBIPC_THROW(, first);
}
#endif
}
/**
* \brief Moves a number of objects to an uninitialized area of memory.
* \see https://en.cppreference.com/w/cpp/memory/uninitialized_move_n
*/
template <typename InputIt, typename Size, typename NoThrowForwardIt>
auto uninitialized_move_n(InputIt first, Size count, NoThrowForwardIt d_first)
-> std::pair<InputIt, NoThrowForwardIt> {
#if defined(LIBIPC_CPP_17)
return std::uninitialized_move_n(first, count, d_first);
#else
using Value = typename std::iterator_traits<NoThrowForwardIt>::value_type;
NoThrowForwardIt current = d_first;
LIBIPC_TRY {
for (; count > 0; ++first, (void) ++current, --count) {
::new (static_cast<void *>(std::addressof(*current))) Value(std::move(*first));
}
} LIBIPC_CATCH(...) {
destroy(d_first, current);
LIBIPC_THROW(, {first, d_first});
}
return {first, current};
#endif
}
} // namespace ipc

View File

@ -2,7 +2,7 @@
#include <string>
#include "libipc/export.h"
#include "libipc/imp/export.h"
#include "libipc/def.h"
#include "libipc/buffer.h"
#include "libipc/shm.h"
@ -18,7 +18,7 @@ enum : unsigned {
};
template <typename Flag>
struct IPC_EXPORT chan_impl {
struct LIBIPC_EXPORT chan_impl {
static ipc::handle_t init_first();
static bool connect (ipc::handle_t * ph, char const * name, unsigned mode);

View File

@ -0,0 +1,121 @@
/**
* \file libipc/block_pool.h
* \author mutouyun (orz@orzz.org)
* \brief The fixed-length memory block pool.
*/
#pragma once
#include <cstddef>
#include "libipc/mem/central_cache_pool.h"
namespace ipc {
namespace mem {
/**
* \brief Fixed-length memory block pool.
* \tparam BlockSize specifies the memory block size
* \tparam BlockPoolExpansion specifies the default number of blocks to expand when the block pool is exhausted
*/
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
class block_pool;
/// \brief General-purpose block pool for any size of memory block.
/// \note This block pool can only be used to deallocate a group of memory blocks of unknown but consistent size,
/// and cannot be used for memory block allocation.
template <>
class block_pool<0, 0> {
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
friend class block_pool;
/// \brief The block type.
struct block_t {
block_t *next;
};
/// \brief The central cache pool type.
using central_cache_pool_t = central_cache_pool<block_t, 0>;
public:
static constexpr std::size_t block_size = 0;
block_pool() noexcept : cursor_(central_cache_pool_t::instance().aqueire()) {}
~block_pool() noexcept {
central_cache_pool_t::instance().release(cursor_);
}
block_pool(block_pool const &) = delete;
block_pool& operator=(block_pool const &) = delete;
block_pool(block_pool &&rhs) noexcept : cursor_(std::exchange(rhs.cursor_, nullptr)) {}
block_pool &operator=(block_pool &&) noexcept = delete;
void deallocate(void *p) noexcept {
if (p == nullptr) return;
block_t *b = static_cast<block_t *>(p);
b->next = cursor_;
cursor_ = b;
}
private:
block_t *cursor_;
};
/// \brief A block pool for a block of memory of a specific size.
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
class block_pool {
/// \brief The block type.
using block_t = block<BlockSize>;
/// \brief The central cache pool type.
using central_cache_pool_t = central_cache_pool<block_t, BlockPoolExpansion>;
/// \brief Expand the block pool when it is exhausted.
block_t *expand() noexcept {
return central_cache_pool_t::instance().aqueire();
}
public:
static constexpr std::size_t block_size = BlockSize;
block_pool() noexcept : cursor_(expand()) {}
~block_pool() noexcept {
central_cache_pool_t::instance().release(cursor_);
}
block_pool(block_pool const &) = delete;
block_pool& operator=(block_pool const &) = delete;
block_pool(block_pool &&rhs) noexcept
: cursor_(std::exchange(rhs.cursor_, nullptr)) {}
block_pool &operator=(block_pool &&) noexcept = delete;
/// \brief Used to take all memory blocks from within a general-purpose block pool.
/// \note Of course, the actual memory blocks they manage must be the same size.
block_pool(block_pool<0, 0> &&rhs) noexcept
: cursor_(reinterpret_cast<block_t *>(std::exchange(rhs.cursor_, nullptr))) {}
void *allocate() noexcept {
if (cursor_ == nullptr) {
cursor_ = expand();
if (cursor_ == nullptr) return nullptr;
}
block_t *p = cursor_;
cursor_ = cursor_->next;
return p->storage.data();
}
void deallocate(void *p) noexcept {
if (p == nullptr) return;
block_t *b = static_cast<block_t *>(p);
b->next = cursor_;
cursor_ = b;
}
private:
block_t *cursor_;
};
} // namespace mem
} // namespace ipc

View File

@ -0,0 +1,163 @@
/**
* \file libipc/bytes_allocator.h
* \author mutouyun (orz@orzz.org)
* \brief A generic polymorphic memory allocator.
*/
#pragma once
#include <type_traits>
#include <array>
#include <limits> // std::numeric_limits
#include <utility> // std::forward
#include <tuple> // std::ignore
#include <cstddef>
#include "libipc/imp/export.h"
#include "libipc/imp/uninitialized.h"
#include "libipc/imp/byte.h"
namespace ipc {
namespace mem {
/// \brief Helper trait for memory resource.
template <typename T, typename = void>
struct has_allocate : std::false_type {};
template <typename T>
struct has_allocate<T,
typename std::enable_if<std::is_convertible<
decltype(std::declval<T &>().allocate(std::declval<std::size_t>(),
std::declval<std::size_t>())), void *
>::value>::type> : std::true_type {};
template <typename T, typename = void>
struct has_deallocate : std::false_type {};
template <typename T>
struct has_deallocate<T,
decltype(std::declval<T &>().deallocate(std::declval<void *>(),
std::declval<std::size_t>(),
std::declval<std::size_t>()))
> : std::true_type {};
template <typename T>
using is_memory_resource =
std::enable_if_t<has_allocate <T>::value &&
has_deallocate<T>::value, bool>;
/**
* \brief An allocator which exhibits different allocation behavior
* depending upon the memory resource from which it is constructed.
*
* \note Unlike `std::pmr::container_allocator`, it does not
* rely on a specific inheritance relationship and only restricts
* the interface behavior of the incoming memory resource object to
* conform to `std::pmr::memory_resource`.
*
* \see https://en.cppreference.com/w/cpp/memory/memory_resource
* https://en.cppreference.com/w/cpp/memory/container_allocator
*/
class LIBIPC_EXPORT bytes_allocator {
class holder_mr_base {
public:
virtual ~holder_mr_base() noexcept = default;
virtual void *alloc(std::size_t, std::size_t) const = 0;
virtual void dealloc(void *, std::size_t, std::size_t) const = 0;
};
template <typename MR, typename = bool>
class holder_mr;
/**
* \brief An empty holding class used to calculate a reasonable memory size for the holder.
* \tparam MR cannot be converted to the type of memory resource
*/
template <typename MR, typename U>
class holder_mr : public holder_mr_base {
protected:
MR *res_;
public:
holder_mr(MR *p_mr) noexcept
: res_(p_mr) {}
// [MSVC] error C2259: 'bytes_allocator::holder_mr<void *,bool>': cannot instantiate abstract class.
void *alloc(std::size_t s, std::size_t a) const override { return nullptr; }
void dealloc(void *p, std::size_t s, std::size_t a) const override {}
};
/**
* \brief A memory resource pointer holder class for type erasure.
* \tparam MR memory resource type
*/
template <typename MR>
class holder_mr<MR, is_memory_resource<MR>> : public holder_mr<MR, void> {
using base_t = holder_mr<MR, void>;
public:
holder_mr(MR *p_mr) noexcept
: base_t{p_mr} {}
void *alloc(std::size_t s, std::size_t a) const override {
return base_t::res_->allocate(s, a);
}
void dealloc(void *p, std::size_t s, std::size_t a) const override {
base_t::res_->deallocate(p, s, a);
}
};
using void_holder_t = holder_mr<void *>;
alignas(void_holder_t) std::array<ipc::byte, sizeof(void_holder_t)> holder_;
holder_mr_base & get_holder() noexcept;
holder_mr_base const &get_holder() const noexcept;
void init_default_resource() noexcept;
public:
/// \brief Constructs an `bytes_allocator` using the return value of
/// `new_delete_resource::get()` as the underlying memory resource.
bytes_allocator() noexcept;
~bytes_allocator() noexcept;
bytes_allocator(bytes_allocator const &other) noexcept = default;
bytes_allocator &operator=(bytes_allocator const &other) & noexcept = default;
bytes_allocator(bytes_allocator &&other) noexcept = default;
bytes_allocator &operator=(bytes_allocator &&other) & noexcept = default;
/// \brief Constructs a `bytes_allocator` from a memory resource pointer.
/// \note The lifetime of the pointer must be longer than that of bytes_allocator.
template <typename T, is_memory_resource<T> = true>
bytes_allocator(T *p_mr) noexcept {
if (p_mr == nullptr) {
init_default_resource();
return;
}
std::ignore = ipc::construct<holder_mr<T>>(holder_.data(), p_mr);
}
void swap(bytes_allocator &other) noexcept;
/// \brief Allocate/deallocate memory.
void *allocate(std::size_t s, std::size_t = alignof(std::max_align_t)) const;
void deallocate(void *p, std::size_t s, std::size_t = alignof(std::max_align_t)) const;
/// \brief Allocates uninitialized memory and constructs an object of type T in the memory.
template <typename T, typename... A>
T *construct(A &&...args) const {
return ipc::construct<T>(allocate(sizeof(T), alignof(T)), std::forward<A>(args)...);
}
/// \brief Calls the destructor of the object pointed to by p and deallocates the memory.
template <typename T>
void destroy(T *p) const noexcept {
deallocate(ipc::destroy(p), sizeof(T), alignof(T));
}
};
} // namespace mem
} // namespace ipc

View File

@ -0,0 +1,20 @@
/**
* \file libipc/central_cache_allocator.h
* \author mutouyun (orz@orzz.org)
* \brief The central cache allocator getter.
*/
#pragma once
#include "libipc/imp/export.h"
#include "libipc/mem/bytes_allocator.h"
namespace ipc {
namespace mem {
/// \brief Get the central cache allocator.
/// \note The central cache allocator is used to allocate memory for the central cache pool.
/// The underlying memory resource is a `monotonic_buffer_resource` with a fixed-size buffer.
LIBIPC_EXPORT bytes_allocator &central_cache_allocator() noexcept;
} // namespace mem
} // namespace ipc

View File

@ -0,0 +1,132 @@
/**
* \file libipc/central_cache_pool.h
* \author mutouyun (orz@orzz.org)
* \brief The fixed-length memory block central cache pool.
*/
#pragma once
#include <cstddef>
#include <deque>
#include <utility>
#include <array>
#include "libipc/imp/byte.h"
#include "libipc/concur/intrusive_stack.h"
#include "libipc/mem/central_cache_allocator.h"
namespace ipc {
namespace mem {
/**
* \brief The block type.
* \tparam BlockSize specifies the memory block size
*/
template <std::size_t BlockSize>
union block {
block *next;
alignas(std::max_align_t) std::array<byte, BlockSize> storage;
};
/**
* \brief A fixed-length memory block central cache pool.
* \tparam BlockT specifies the memory block type
*/
template <typename BlockT, std::size_t BlockPoolExpansion>
class central_cache_pool {
/// \brief The block type, which should be a union of a pointer and a storage.
using block_t = BlockT;
/// \brief The chunk type, which is an array of blocks.
using chunk_t = std::array<block_t, BlockPoolExpansion>;
/// \brief The node type, which is used to store the block pointer.
using node_t = typename concur::intrusive_stack<block_t *>::node;
/// \brief The central cache stack.
concur::intrusive_stack<block_t *> cached_;
concur::intrusive_stack<block_t *> aqueired_;
central_cache_pool() noexcept = default;
public:
block_t *aqueire() noexcept {
auto *n = cached_.pop();
if (n != nullptr) {
aqueired_.push(n);
return n->value;
}
auto *chunk = central_cache_allocator().construct<chunk_t>();
if (chunk == nullptr) {
return nullptr;
}
for (std::size_t i = 0; i < BlockPoolExpansion - 1; ++i) {
(*chunk)[i].next = &(*chunk)[i + 1];
}
chunk->back().next = nullptr;
return chunk->data();
}
void release(block_t *p) noexcept {
if (p == nullptr) return;
auto *a = aqueired_.pop();
if (a == nullptr) {
a = central_cache_allocator().construct<node_t>();
if (a == nullptr) return;
}
a->value = p;
cached_.push(a);
}
/// \brief Get the singleton instance.
static central_cache_pool &instance() noexcept {
static central_cache_pool pool;
return pool;
}
};
/// \brief A fixed-length memory block central cache pool with no default expansion size.
template <typename BlockT>
class central_cache_pool<BlockT, 0> {
/// \brief The block type, which should be a union of a pointer and a storage.
using block_t = BlockT;
/// \brief The node type, which is used to store the block pointer.
using node_t = typename concur::intrusive_stack<block_t *>::node;
/// \brief The central cache stack.
concur::intrusive_stack<block_t *> cached_;
concur::intrusive_stack<block_t *> aqueired_;
central_cache_pool() noexcept = default;
public:
block_t *aqueire() noexcept {
auto *n = cached_.pop();
if (n != nullptr) {
aqueired_.push(n);
return n->value;
}
// For pools with no default expansion size,
// the central cache pool is only buffered, not allocated.
return nullptr;
}
void release(block_t *p) noexcept {
if (p == nullptr) return;
auto *a = aqueired_.pop();
if (a == nullptr) {
a = central_cache_allocator().construct<node_t>();
if (a == nullptr) return;
}
a->value = p;
cached_.push(a);
}
/// \brief Get the singleton instance.
static central_cache_pool &instance() noexcept {
static central_cache_pool pool;
return pool;
}
};
} // namespace mem
} // namespace ipc

View File

@ -0,0 +1,101 @@
/**
* \file libipc/container_allocator.h
* \author mutouyun (orz@orzz.org)
* \brief An allocator that can be used by all standard library containers.
*/
#pragma once
#include <cstddef>
#include <utility>
#include <limits>
#include "libipc/imp/uninitialized.h"
#include "libipc/mem/new.h"
namespace ipc {
namespace mem {
/**
* \brief An allocator that can be used by all standard library containers.
*
* \see https://en.cppreference.com/w/cpp/memory/allocator
* https://en.cppreference.com/w/cpp/memory/polymorphic_allocator
*/
template <typename T>
class container_allocator {
template <typename U>
friend class container_allocator;
public:
// type definitions
typedef T value_type;
typedef value_type * pointer;
typedef const value_type *const_pointer;
typedef value_type & reference;
typedef const value_type &const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
// the other type of std_allocator
template <typename U>
struct rebind {
using other = container_allocator<U>;
};
container_allocator() noexcept {}
// construct by copying (do nothing)
container_allocator (container_allocator<T> const &) noexcept {}
container_allocator& operator=(container_allocator<T> const &) noexcept { return *this; }
// construct from a related allocator (do nothing)
template <typename U> container_allocator (container_allocator<U> const &) noexcept {}
template <typename U> container_allocator &operator=(container_allocator<U> const &) noexcept { return *this; }
container_allocator (container_allocator &&) noexcept = default;
container_allocator& operator=(container_allocator &&) noexcept = default;
constexpr size_type max_size() const noexcept {
return (std::numeric_limits<size_type>::max)() / sizeof(value_type);
}
pointer allocate(size_type count) noexcept {
if (count == 0) return nullptr;
if (count > this->max_size()) return nullptr;
// Allocate raw memory without constructing objects
// Construction should be done by construct() member function
void *p = mem::alloc(sizeof(value_type) * count);
return static_cast<pointer>(p);
}
void deallocate(pointer p, size_type count) noexcept {
if (count == 0) return;
if (count > this->max_size()) return;
// Deallocate raw memory without destroying objects
// Destruction should be done by destroy() member function before deallocate
mem::free(p, sizeof(value_type) * count);
}
template <typename... P>
static void construct(pointer p, P && ... params) {
std::ignore = ipc::construct<T>(p, std::forward<P>(params)...);
}
static void destroy(pointer p) {
std::ignore = ipc::destroy(p);
}
};
template <typename T, typename U>
constexpr bool operator==(container_allocator<T> const &, container_allocator<U> const &) noexcept {
return true;
}
template <typename T, typename U>
constexpr bool operator!=(container_allocator<T> const &, container_allocator<U> const &) noexcept {
return false;
}
} // namespace mem
} // namespace ipc

View File

@ -0,0 +1,83 @@
/**
* \file libipc/memory_resource.h
* \author mutouyun (orz@orzz.org)
* \brief Implement memory allocation strategies that can be used by ipc::mem::bytes_allocator.
*/
#pragma once
#include <type_traits>
#include <cstddef> // std::size_t, std::max_align_t
#include "libipc/imp/export.h"
#include "libipc/imp/span.h"
#include "libipc/imp/byte.h"
#include "libipc/mem/bytes_allocator.h"
namespace ipc {
namespace mem {
/**
* \class LIBIPC_EXPORT new_delete_resource
* \brief A memory resource that uses the
* standard memory allocation and deallocation interface to allocate memory.
* \see https://en.cppreference.com/w/cpp/memory/new_delete_resource
*/
class LIBIPC_EXPORT new_delete_resource {
public:
/// \brief Returns a pointer to a `new_delete_resource`.
static new_delete_resource *get() noexcept;
/// \brief Allocates storage with a size of at least bytes bytes, aligned to the specified alignment.
/// \remark Returns nullptr if storage of the requested size and alignment cannot be obtained.
/// \see https://en.cppreference.com/w/cpp/memory/memory_resource/do_allocate
void *allocate(std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept;
/// \brief Deallocates the storage pointed to by p.
/// \see https://en.cppreference.com/w/cpp/memory/memory_resource/deallocate
void deallocate(void *p, std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept;
};
/**
* \class LIBIPC_EXPORT monotonic_buffer_resource
* \brief A special-purpose memory resource class
* that releases the allocated memory only when the resource is destroyed.
* \see https://en.cppreference.com/w/cpp/memory/monotonic_buffer_resource
*/
class LIBIPC_EXPORT monotonic_buffer_resource {
bytes_allocator upstream_;
struct node {
node *next;
std::size_t size;
} *free_list_;
ipc::byte * head_;
ipc::byte * tail_;
std::size_t next_size_;
ipc::byte * const initial_buffer_;
std::size_t const initial_size_;
public:
monotonic_buffer_resource() noexcept;
explicit monotonic_buffer_resource(bytes_allocator upstream) noexcept;
explicit monotonic_buffer_resource(std::size_t initial_size) noexcept;
monotonic_buffer_resource(std::size_t initial_size, bytes_allocator upstream) noexcept;
monotonic_buffer_resource(ipc::span<ipc::byte> buffer) noexcept;
monotonic_buffer_resource(ipc::span<ipc::byte> buffer, bytes_allocator upstream) noexcept;
~monotonic_buffer_resource() noexcept;
monotonic_buffer_resource(monotonic_buffer_resource const &) = delete;
monotonic_buffer_resource &operator=(monotonic_buffer_resource const &) = delete;
bytes_allocator upstream_resource() const noexcept;
void release() noexcept;
void *allocate(std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept;
void deallocate(void *p, std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept;
};
} // namespace mem
} // namespace ipc

117
include/libipc/mem/new.h Normal file
View File

@ -0,0 +1,117 @@
/**
* \file libipc/mem.h
* \author mutouyun (orz@orzz.org)
* \brief Global memory management.
*/
#pragma once
#include <cstddef>
#include <algorithm>
#include <type_traits>
#include <limits>
#include <tuple>
#include "libipc/imp/aligned.h"
#include "libipc/imp/uninitialized.h"
#include "libipc/imp/byte.h"
#include "libipc/imp/detect_plat.h"
#include "libipc/imp/export.h"
#include "libipc/mem/memory_resource.h"
#include "libipc/mem/block_pool.h"
namespace ipc {
namespace mem {
/// \brief Defines the memory block collector interface.
class LIBIPC_EXPORT block_collector {
public:
virtual ~block_collector() noexcept = default;
virtual void *allocate(std::size_t /*bytes*/) noexcept = 0;
virtual void deallocate(void * /*p*/, std::size_t /*bytes*/) noexcept = 0;
};
/// \brief Matches the appropriate memory block resource based on a specified size.
LIBIPC_EXPORT block_collector &get_regular_resource(std::size_t s) noexcept;
/// \brief Allocates storage with a size of at least bytes bytes.
LIBIPC_EXPORT void *alloc(std::size_t bytes) noexcept;
LIBIPC_EXPORT void free (void *p, std::size_t bytes) noexcept;
namespace detail_new {
#if defined(LIBIPC_CPP_17)
using recycle_t = void (*)(void *p) noexcept;
#else
using recycle_t = void (*)(void *p);
#endif
static constexpr std::size_t recycler_size = round_up(sizeof(recycle_t), alignof(std::size_t));
static constexpr std::size_t allocated_size = sizeof(std::size_t);
static constexpr std::size_t regular_head_size = round_up(recycler_size + allocated_size, alignof(std::max_align_t));
template <typename T>
struct do_allocate {
template <typename... A>
static T *apply(A &&... args) noexcept {
void *b = mem::alloc(regular_head_size + sizeof(T));
auto *p = static_cast<byte *>(b) + regular_head_size;
LIBIPC_TRY {
T *t = construct<T>(p, std::forward<A>(args)...);
*reinterpret_cast<recycle_t *>(b)
= [](void *p) noexcept {
mem::free(static_cast<byte *>(destroy(static_cast<T *>(p))) - regular_head_size
, regular_head_size + sizeof(T));
};
return t;
} LIBIPC_CATCH(...) {
return nullptr;
}
}
};
template <>
struct do_allocate<void> {
static void *apply(std::size_t bytes) noexcept {
if (bytes == 0) return nullptr;
std::size_t rbz = regular_head_size + bytes;
void *b = mem::alloc(rbz);
*reinterpret_cast<recycle_t *>(b)
= [](void *p) noexcept {
auto *b = static_cast<byte *>(p) - regular_head_size;
mem::free(b, *reinterpret_cast<std::size_t *>(b + recycler_size));
};
auto *z = static_cast<byte *>(b) + recycler_size;
*reinterpret_cast<std::size_t *>(z) = rbz;
return static_cast<byte *>(b) + regular_head_size;
}
};
} // namespace detail_new
/// \brief Creates an object based on the specified type and parameters with block pool resource.
/// \note This function is thread-safe.
template <typename T, typename... A>
T *$new(A &&... args) noexcept {
return detail_new::do_allocate<T>::apply(std::forward<A>(args)...);
}
/// \brief Destroys object previously allocated by the `$new` and releases obtained memory area.
/// \note This function is thread-safe. If the pointer type passed in is different from `$new`,
/// additional performance penalties may be incurred.
inline void $delete(void *p) noexcept {
if (p == nullptr) return;
auto *r = reinterpret_cast<detail_new::recycle_t *>(static_cast<byte *>(p) - detail_new::regular_head_size);
(*r)(p);
}
/// \brief The destruction policy used by std::unique_ptr.
/// \see https://en.cppreference.com/w/cpp/memory/default_delete
struct deleter {
template <typename T>
void operator()(T *p) const noexcept {
$delete(p);
}
};
} // namespace mem
} // namespace ipc

View File

@ -3,13 +3,13 @@
#include <cstdint> // std::uint64_t
#include <system_error>
#include "libipc/export.h"
#include "libipc/imp/export.h"
#include "libipc/def.h"
namespace ipc {
namespace sync {
class IPC_EXPORT mutex {
class LIBIPC_EXPORT mutex {
mutex(mutex const &) = delete;
mutex &operator=(mutex const &) = delete;

View File

@ -1,103 +0,0 @@
#pragma once
#include <new>
#include <utility>
#include "libipc/export.h"
#include "libipc/def.h"
namespace ipc {
namespace mem {
class IPC_EXPORT pool_alloc {
public:
static void* alloc(std::size_t size) noexcept;
static void free (void* p, std::size_t size) noexcept;
};
////////////////////////////////////////////////////////////////
/// construct/destruct an object
////////////////////////////////////////////////////////////////
namespace detail {
template <typename T>
struct impl {
template <typename... P>
static T* construct(T* p, P&&... params) {
::new (p) T(std::forward<P>(params)...);
return p;
}
static void destruct(T* p) {
reinterpret_cast<T*>(p)->~T();
}
};
template <typename T, size_t N>
struct impl<T[N]> {
using type = T[N];
template <typename... P>
static type* construct(type* p, P&&... params) {
for (size_t i = 0; i < N; ++i) {
impl<T>::construct(&((*p)[i]), std::forward<P>(params)...);
}
return p;
}
static void destruct(type* p) {
for (size_t i = 0; i < N; ++i) {
impl<T>::destruct(&((*p)[i]));
}
}
};
} // namespace detail
template <typename T, typename... P>
T* construct(T* p, P&&... params) {
return detail::impl<T>::construct(p, std::forward<P>(params)...);
}
template <typename T, typename... P>
T* construct(void* p, P&&... params) {
return construct(static_cast<T*>(p), std::forward<P>(params)...);
}
template <typename T>
void destruct(T* p) {
return detail::impl<T>::destruct(p);
}
template <typename T>
void destruct(void* p) {
destruct(static_cast<T*>(p));
}
////////////////////////////////////////////////////////////////
/// general alloc/free
////////////////////////////////////////////////////////////////
inline void* alloc(std::size_t size) {
return pool_alloc::alloc(size);
}
template <typename T, typename... P>
T* alloc(P&&... params) {
return construct<T>(pool_alloc::alloc(sizeof(T)), std::forward<P>(params)...);
}
inline void free(void* p, std::size_t size) {
pool_alloc::free(p, size);
}
template <typename T>
void free(T* p) {
if (p == nullptr) return;
destruct(p);
pool_alloc::free(p, sizeof(T));
}
} // namespace mem
} // namespace ipc

View File

@ -6,6 +6,7 @@
#include <limits>
#include <type_traits>
#include <utility>
#include <cstdint>
////////////////////////////////////////////////////////////////
/// Gives hint to processor that improves performance of spin-wait loops.
@ -98,7 +99,7 @@ inline void sleep(K& k) {
namespace ipc {
class spin_lock {
std::atomic<unsigned> lc_ { 0 };
std::atomic<std::uint32_t> lc_ { 0 };
public:
void lock(void) noexcept {
@ -113,13 +114,13 @@ public:
};
class rw_lock {
using lc_ui_t = unsigned;
using lc_ui_t = std::uint32_t;
std::atomic<lc_ui_t> lc_ { 0 };
enum : lc_ui_t {
w_mask = (std::numeric_limits<std::make_signed_t<lc_ui_t>>::max)(), // b 0111 1111
w_flag = w_mask + 1 // b 1000 0000
w_mask = (std::numeric_limits<std::make_signed_t<lc_ui_t>>::max)(), // b 0111 1111 ...
w_flag = w_mask + 1 // b 1000 0000 ...
};
public:

View File

@ -2,13 +2,13 @@
#include <cstdint> // std::uint64_t
#include "libipc/export.h"
#include "libipc/imp/export.h"
#include "libipc/def.h"
namespace ipc {
namespace sync {
class IPC_EXPORT semaphore {
class LIBIPC_EXPORT semaphore {
semaphore(semaphore const &) = delete;
semaphore &operator=(semaphore const &) = delete;

View File

@ -3,7 +3,7 @@
#include <cstddef>
#include <cstdint>
#include "libipc/export.h"
#include "libipc/imp/export.h"
namespace ipc {
namespace shm {
@ -15,16 +15,38 @@ enum : unsigned {
open = 0x02
};
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 std::int32_t release(id_t id) noexcept;
IPC_EXPORT void remove (id_t id) noexcept;
IPC_EXPORT void remove (char const * name) noexcept;
LIBIPC_EXPORT id_t acquire(char const * name, std::size_t size, unsigned mode = create | open);
LIBIPC_EXPORT void * get_mem(id_t id, std::size_t * size);
IPC_EXPORT std::int32_t get_ref(id_t id);
IPC_EXPORT void sub_ref(id_t id);
// Release shared memory resource and clean up disk file if reference count reaches zero.
// This function decrements the reference counter. When the counter reaches zero, it:
// 1. Unmaps the shared memory region
// 2. Removes the backing file from disk (shm_unlink on POSIX)
// 3. Frees the id structure
// After calling this function, the id becomes invalid and must not be used again.
// Returns: The reference count before decrement, or -1 on error.
LIBIPC_EXPORT std::int32_t release(id_t id) noexcept;
class IPC_EXPORT handle {
// 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.
LIBIPC_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.
LIBIPC_EXPORT void remove (char const * name) noexcept;
LIBIPC_EXPORT std::int32_t get_ref(id_t id);
LIBIPC_EXPORT void sub_ref(id_t id);
class LIBIPC_EXPORT handle {
public:
handle();
handle(char const * name, std::size_t size, unsigned mode = create | open);
@ -61,3 +83,4 @@ private:
} // namespace shm
} // namespace ipc

View File

@ -5,6 +5,8 @@ set (PACKAGE_VERSION 1.3.0)
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc SRC_FILES)
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/sync SRC_FILES)
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/platform SRC_FILES)
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/imp SRC_FILES)
aux_source_directory(${LIBIPC_PROJECT_DIR}/src/libipc/mem SRC_FILES)
file(GLOB HEAD_FILES
${LIBIPC_PROJECT_DIR}/include/libipc/*.h

View File

@ -38,16 +38,16 @@ buffer::buffer(void* p, std::size_t s, destructor_t d)
: p_(p_->make(p, s, d, nullptr)) {
}
buffer::buffer(void* p, std::size_t s, destructor_t d, void* additional)
: p_(p_->make(p, s, d, additional)) {
buffer::buffer(void* p, std::size_t s, destructor_t d, void* mem_to_free)
: p_(p_->make(p, s, d, mem_to_free)) {
}
buffer::buffer(void* p, std::size_t s)
: buffer(p, s, nullptr) {
}
buffer::buffer(char const & c)
: buffer(const_cast<char*>(&c), 1) {
buffer::buffer(char & c)
: buffer(&c, 1) {
}
buffer::buffer(buffer&& rhs)

View File

@ -33,7 +33,7 @@ public:
void init() {
/* DCLP */
if (!constructed_.load(std::memory_order_acquire)) {
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lc_);
LIBIPC_UNUSED auto guard = ipc::detail::unique_lock(lc_);
if (!constructed_.load(std::memory_order_relaxed)) {
::new (this) conn_head_base;
constructed_.store(true, std::memory_order_release);
@ -60,7 +60,7 @@ public:
for (unsigned k = 0;; ipc::yield(k)) {
cc_t curr = this->cc_.load(std::memory_order_acquire);
cc_t next = curr | (curr + 1); // find the first 0, and set it to 1.
if (next == 0) {
if (next == curr) {
// connection-slot is full.
return 0;
}
@ -74,6 +74,10 @@ public:
return this->cc_.fetch_and(~cc_id, std::memory_order_acq_rel) & ~cc_id;
}
bool connected(cc_t cc_id) const noexcept {
return (this->connections() & cc_id) != 0;
}
std::size_t conn_count(std::memory_order order = std::memory_order_acquire) const noexcept {
cc_t cur = this->cc_.load(order);
cc_t cnt; // accumulates the total bits set in cc
@ -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 {
return this->connections(order);
}

365
src/libipc/imp/codecvt.cpp Normal file
View File

@ -0,0 +1,365 @@
#include <algorithm>
#include <type_traits>
#include <cstring>
#include <cstdint>
#include "libipc/imp/codecvt.h"
#include "libipc/imp/detect_plat.h"
#if defined(LIBIPC_OS_WIN)
# include "libipc/platform/win/codecvt.h"
#endif
namespace ipc {
/**
* \brief The transform between local-character-set(UTF-8/GBK/...) and UTF-16/32.
*
* Modified from UnicodeConverter.
* Copyright (c) 2010. Jianhui Qin (http://blog.csdn.net/jhqin).
*
* \remarks codecvt_utf8_utf16/std::wstring_convert is deprecated.
* \see https://codingtidbit.com/2020/02/09/c17-codecvt_utf8-is-deprecated/
* https://stackoverflow.com/questions/42946335/deprecated-header-codecvt-replacement
* https://en.cppreference.com/w/cpp/locale/codecvt/in
*/
namespace {
/// \brief X-bit unicode transformation format
enum class ufmt {
utf8,
utf16,
utf32,
};
template <typename T, ufmt, typename = void>
struct utf_compatible : std::false_type {};
template <typename T>
struct utf_compatible<T, ufmt::utf8,
std::enable_if_t<std::is_fundamental<T>::value && (sizeof(T) == 1)>> : std::true_type {};
template <typename T>
struct utf_compatible<T, ufmt::utf16,
std::enable_if_t<std::is_fundamental<T>::value && (sizeof(T) == 2)>> : std::true_type {};
template <typename T>
struct utf_compatible<T, ufmt::utf32,
std::enable_if_t<std::is_fundamental<T>::value && (sizeof(T) == 4)>> : std::true_type {};
template <typename T, ufmt Fmt>
constexpr bool utf_compatible_v = utf_compatible<T, Fmt>::value;
/**
* \brief UTF-32 --> UTF-8
*/
template <typename T, typename U>
auto cvt_char(T src, U* des, std::size_t dlen) noexcept
-> std::enable_if_t<utf_compatible_v<T, ufmt::utf32> &&
utf_compatible_v<U, ufmt::utf8>, std::size_t> {
if (src == 0) return 0;
constexpr std::uint8_t prefix[] = {
0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC
};
constexpr std::uint32_t codeup[] = {
0x80, // U+00000000 - U+0000007F
0x800, // U+00000080 - U+000007FF
0x10000, // U+00000800 - U+0000FFFF
0x200000, // U+00010000 - U+001FFFFF
0x4000000, // U+00200000 - U+03FFFFFF
0x80000000 // U+04000000 - U+7FFFFFFF
};
std::size_t i, len = sizeof(codeup) / sizeof(std::uint32_t);
for(i = 0; i < len; ++i) {
if (static_cast<std::uint32_t>(src) < codeup[i]) break;
}
if (i == len) return 0; // the src is invalid
len = i + 1;
if (des != nullptr) {
if (dlen > i) for (; i > 0; --i) {
des[i] = static_cast<U>((src & 0x3F) | 0x80);
src >>= 6;
}
des[0] = static_cast<U>(src | prefix[len - 1]);
}
return len;
}
/**
* \brief UTF-8 --> UTF-32
*/
template <typename T, typename U>
auto cvt_char(T const *src, std::size_t slen, U &des) noexcept
-> std::enable_if_t<utf_compatible_v<T, ufmt::utf8> &&
utf_compatible_v<U, ufmt::utf32>, std::size_t> {
if ((src == nullptr) || (*src) == 0) return 0;
if (slen == 0) return 0;
std::uint8_t b = (std::uint8_t)*(src++);
if (b < 0x80) {
des = b;
return 1;
}
if (b < 0xC0 || b > 0xFD) return 0; // the src is invalid
std::size_t len;
if (b < 0xE0) {
des = b & 0x1F;
len = 2;
} else if (b < 0xF0) {
des = b & 0x0F;
len = 3;
} else if (b < 0xF8) {
des = b & 0x07;
len = 4;
} else if (b < 0xFC) {
des = b & 0x03;
len = 5;
} else {
des = b & 0x01;
len = 6;
}
if (slen < len) return 0;
std::size_t i = 1;
for(; i < len; ++i) {
b = *(src++);
if ((b < 0x80) || (b > 0xBF)) return 0; // the src is invalid
des = (des << 6) + (b & 0x3F);
}
return len;
}
/**
* \brief UTF-32 --> UTF-16
*/
template <typename T, typename U>
auto cvt_char(T src, U *des, std::size_t dlen) noexcept
-> std::enable_if_t<utf_compatible_v<T, ufmt::utf32> &&
utf_compatible_v<U, ufmt::utf16>, std::size_t> {
if (src == 0) return 0;
if (src <= 0xFFFF) {
if ((des != nullptr) && (dlen != 0)) {
(*des) = static_cast<U>(src);
}
return 1;
} else if (src <= 0xEFFFF) {
if ((des != nullptr) && (dlen > 1)) {
des[0] = static_cast<U>(0xD800 + (src >> 10) - 0x40); // high
des[1] = static_cast<U>(0xDC00 + (src & 0x03FF)); // low
}
return 2;
}
return 0;
}
/**
* \brief UTF-16 --> UTF-32
*/
template <typename T, typename U>
auto cvt_char(T const *src, std::size_t slen, U &des)
-> std::enable_if_t<utf_compatible_v<T, ufmt::utf16> &&
utf_compatible_v<U, ufmt::utf32>, std::size_t> {
if ((src == nullptr) || (*src) == 0) return 0;
if (slen == 0) return 0;
std::uint16_t w1 = src[0];
if ((w1 >= 0xD800) && (w1 <= 0xDFFF)) {
if (w1 < 0xDC00) {
if (slen < 2) return 0;
std::uint16_t w2 = src[1];
if ((w2 >= 0xDC00) && (w2 <= 0xDFFF)) {
des = (w2 & 0x03FF) + (((w1 & 0x03FF) + 0x40) << 10);
return 2;
}
}
return 0; // the src is invalid
}
des = w1;
return 1;
}
/**
* \brief UTF-16 --> UTF-8
*/
template <typename T, typename U>
auto cvt_char(T src, U *des, std::size_t dlen) noexcept
-> std::enable_if_t<utf_compatible_v<T, ufmt::utf16> &&
utf_compatible_v<U, ufmt::utf8>, std::size_t> {
// make utf-16 to utf-32
std::uint32_t tmp;
if (cvt_char(&src, 1, tmp) != 1) return 0;
// make utf-32 to utf-8
return cvt_char(tmp, des, dlen);
}
/**
* \brief UTF-8 --> UTF-16
*/
template <typename T, typename U>
auto cvt_char(T const *src, std::size_t slen, U &des)
-> std::enable_if_t<utf_compatible_v<T, ufmt::utf8> &&
utf_compatible_v<U, ufmt::utf16>, std::size_t> {
// make utf-8 to utf-32
std::uint32_t tmp;
std::size_t len = cvt_char(src, slen, tmp);
if (len == 0) return 0;
// make utf-32 to utf-16
if (cvt_char(tmp, &des, 1) != 1) return 0;
return len;
}
/**
* \brief UTF-32 string --> UTF-8/16 string
*/
template <typename T, typename U>
auto cvt_cstr_utf(T const *src, std::size_t slen, U *des, std::size_t dlen) noexcept
-> std::enable_if_t<utf_compatible_v<T, ufmt::utf32> &&
(utf_compatible_v<U, ufmt::utf16> || utf_compatible_v<U, ufmt::utf8>), std::size_t> {
if ((src == nullptr) || ((*src) == 0) || (slen == 0)) {
// source string is empty
return 0;
}
std::size_t num = 0, len = 0;
for (std::size_t i = 0; (i < slen) && ((*src) != 0); ++src, ++i) {
len = cvt_char(*src, des, dlen);
if (len == 0) return 0;
if (des != nullptr) {
des += len;
if (dlen < len) {
dlen = 0;
} else {
dlen -= len;
}
}
num += len;
}
return num;
}
/**
* \brief UTF-8/16 string --> UTF-32 string
*/
template <typename T, typename U>
auto cvt_cstr_utf(T const *src, std::size_t slen, U *des, std::size_t dlen) noexcept
-> std::enable_if_t<utf_compatible_v<U, ufmt::utf32> &&
(utf_compatible_v<T, ufmt::utf16> || utf_compatible_v<T, ufmt::utf8>), std::size_t> {
if ((src == nullptr) || ((*src) == 0) || (slen == 0)) {
// source string is empty
return 0;
}
std::size_t num = 0;
for (std::size_t i = 0; (i < slen) && ((*src) != 0);) {
std::uint32_t tmp;
std::size_t len = cvt_char(src, slen - i, tmp);
if (len == 0) return 0;
if ((des != nullptr) && (dlen > 0)) {
(*des) = tmp;
++des;
dlen -= 1;
}
src += len;
i += len;
num += 1;
}
return num;
}
/**
* \brief UTF-8/16 string --> UTF-16/8 string
*/
template <typename T, typename U>
auto cvt_cstr_utf(T const *src, std::size_t slen, U *des, std::size_t dlen) noexcept
-> std::enable_if_t<(utf_compatible_v<T, ufmt::utf8> && utf_compatible_v<U, ufmt::utf16>) ||
(utf_compatible_v<T, ufmt::utf16> && utf_compatible_v<U, ufmt::utf8>), std::size_t> {
if ((src == nullptr) || ((*src) == 0) || (slen == 0)) {
// source string is empty
return 0;
}
std::size_t num = 0;
for (std::size_t i = 0; (i < slen) && ((*src) != 0);) {
// make utf-x to utf-32
std::uint32_t tmp;
std::size_t len = cvt_char(src, slen - i, tmp);
if (len == 0) return 0;
src += len;
i += len;
// make utf-32 to utf-y
len = cvt_char(tmp, des, dlen);
if (len == 0) return 0;
if (des != nullptr) {
des += len;
if (dlen < len) {
dlen = 0;
} else {
dlen -= len;
}
}
num += len;
}
return num;
}
template <typename T, typename U>
auto cvt_cstr_utf(T const *src, std::size_t slen, U *des, std::size_t dlen) noexcept
-> std::enable_if_t<(sizeof(T) == sizeof(U)), std::size_t> {
if ((des == nullptr) || (dlen == 0)) {
return slen;
}
std::size_t r = (std::min)(slen, dlen);
std::memcpy(des, src, r * sizeof(T));
return r;
}
} // namespace
#define LIBIPC_DEF_CVT_CSTR_($CHAR_T, $CHAR_U) \
template <> \
std::size_t cvt_cstr($CHAR_T const *src, std::size_t slen, $CHAR_U *des, std::size_t dlen) noexcept { \
return cvt_cstr_utf(src, slen, des, dlen); \
}
// #define LIBIPC_DEF_CVT_CSTR_($CHAR_T, $CHAR_U)
LIBIPC_DEF_CVT_CSTR_(char , char)
LIBIPC_DEF_CVT_CSTR_(char , char16_t)
LIBIPC_DEF_CVT_CSTR_(char , char32_t)
LIBIPC_DEF_CVT_CSTR_(wchar_t , wchar_t)
LIBIPC_DEF_CVT_CSTR_(char16_t, char16_t)
LIBIPC_DEF_CVT_CSTR_(char16_t, char)
LIBIPC_DEF_CVT_CSTR_(char16_t, char32_t)
LIBIPC_DEF_CVT_CSTR_(char32_t, char32_t)
LIBIPC_DEF_CVT_CSTR_(char32_t, char)
LIBIPC_DEF_CVT_CSTR_(char32_t, char16_t)
#if !defined(LIBIPC_OS_WIN)
LIBIPC_DEF_CVT_CSTR_(char , wchar_t)
LIBIPC_DEF_CVT_CSTR_(wchar_t , char)
LIBIPC_DEF_CVT_CSTR_(wchar_t , char16_t)
LIBIPC_DEF_CVT_CSTR_(wchar_t , char32_t)
LIBIPC_DEF_CVT_CSTR_(char16_t, wchar_t)
LIBIPC_DEF_CVT_CSTR_(char32_t, wchar_t)
#endif // !defined(LIBIPC_OS_WIN)
#if defined(LIBIPC_CPP_20)
LIBIPC_DEF_CVT_CSTR_(char8_t , char8_t)
LIBIPC_DEF_CVT_CSTR_(char8_t , char)
LIBIPC_DEF_CVT_CSTR_(char8_t , char16_t)
LIBIPC_DEF_CVT_CSTR_(char8_t , char32_t)
LIBIPC_DEF_CVT_CSTR_(char , char8_t)
LIBIPC_DEF_CVT_CSTR_(char16_t, char8_t)
LIBIPC_DEF_CVT_CSTR_(char32_t, char8_t)
#if !defined(LIBIPC_OS_WIN)
LIBIPC_DEF_CVT_CSTR_(char8_t , wchar_t)
LIBIPC_DEF_CVT_CSTR_(wchar_t , char8_t)
#endif // !defined(LIBIPC_OS_WIN)
#endif // defined(LIBIPC_CPP_20)
#undef LIBIPC_DEF_CVT_CSTR_
} // namespace ipc

329
src/libipc/imp/fmt.cpp Normal file
View File

@ -0,0 +1,329 @@
#include <cstdio> // std::snprintf
#include <iomanip> // std::put_time
#include <sstream> // std::ostringstream
#include <array>
#include <cstring> // std::memcpy
#include <algorithm> // std::min
#include <initializer_list>
#include <cstdint>
#include "libipc/imp/fmt.h"
#include "libipc/imp/codecvt.h"
#include "libipc/imp/detect_plat.h"
namespace ipc {
/**
* \brief Format conversions helpers.
* \see http://personal.ee.surrey.ac.uk/Personal/R.Bowden/C/printf.html
* https://en.cppreference.com/w/cpp/io/c/fprintf
*/
namespace {
struct sfmt_policy {
static constexpr std::size_t aligned_size = 32U;
};
template <typename Policy = sfmt_policy>
span<char> local_fmt_sbuf() noexcept {
thread_local std::array<char, Policy::aligned_size> sbuf;
return sbuf;
}
span<char const> normalize(span<char const> const &a) {
if (a.empty()) return {};
return a.first(a.size() - (a.back() == '\0' ? 1 : 0));
}
span<char> smem_cpy(span<char> const &sbuf, span<char const> a) noexcept {
if (sbuf.empty()) return {};
a = normalize(a);
auto sz = (std::min)(sbuf.size() - 1, a.size());
if (sz != 0) std::memcpy(sbuf.data(), a.data(), sz);
return sbuf.first(sz);
}
span<char> sbuf_cpy(span<char> sbuf, span<char const> const &a) noexcept {
sbuf = smem_cpy(sbuf, a);
*sbuf.end() = '\0';
return sbuf;
}
span<char> sbuf_cat(span<char> const &sbuf, std::initializer_list<span<char const>> args) noexcept {
std::size_t remain = sbuf.size();
for (auto s : args) {
remain -= smem_cpy(sbuf.last(remain), s).size();
}
auto sz = sbuf.size() - remain;
sbuf[sz] = '\0';
return sbuf.first(sz);
}
char const *as_cstr(span<char const> const &a) {
if (a.empty()) return "";
if (a.back() == '\0') return a.data();
return sbuf_cpy(local_fmt_sbuf(), a).data();
}
span<char> fmt_of(span<char const> const &fstr, span<char const> const &s) {
return sbuf_cat(local_fmt_sbuf(), {"%", fstr, s});
}
span<char> fmt_of_unsigned(span<char const> fstr, span<char const> const &l) {
if (fstr.empty()) {
return fmt_of(l, "u");
}
fstr = normalize(fstr);
switch (fstr.back()) {
case 'o':
case 'x':
case 'X':
case 'u': return sbuf_cat(local_fmt_sbuf(), {"%", fstr.first(fstr.size() - 1), l, fstr.last(1)});
default : return sbuf_cat(local_fmt_sbuf(), {"%", fstr, l, "u"});
}
}
span<char> fmt_of_signed(span<char const> fstr, span<char const> const &l) {
if (fstr.empty()) {
return fmt_of(l, "d");
}
fstr = normalize(fstr);
switch (fstr.back()) {
case 'o':
case 'x':
case 'X':
case 'u': return fmt_of_unsigned(fstr, l);
default : return sbuf_cat(local_fmt_sbuf(), {"%", fstr, l, "d"});
}
}
span<char> fmt_of_float(span<char const> fstr, span<char const> const &l) {
if (fstr.empty()) {
return fmt_of(l, "f");
}
fstr = normalize(fstr);
switch (fstr.back()) {
case 'e':
case 'E':
case 'g':
case 'G': return sbuf_cat(local_fmt_sbuf(), {"%", fstr.first(fstr.size() - 1), l, fstr.last(1)});
default : return sbuf_cat(local_fmt_sbuf(), {"%", fstr, l, "f"});
}
}
template <typename A /*a fundamental or pointer type*/>
int sprintf(fmt_context &ctx, span<char const> const &sfmt, A a) {
for (std::int32_t sz = -1;;) {
auto sbuf = ctx.buffer(sz + 1);
if (sbuf.size() < std::size_t(sz + 1)) {
return -1;
}
sz = std::snprintf(sbuf.data(), sbuf.size(), sfmt.data(), a);
if (sz <= 0) {
return sz;
}
if (std::size_t(sz) < sbuf.size()) {
ctx.expend(sz);
return sz;
}
}
}
template <typename F /*a function pointer*/,
typename A /*a fundamental or pointer type*/>
bool sprintf(fmt_context &ctx, F fop, span<char const> const &fstr, span<char const> const &s, A a) noexcept {
LIBIPC_TRY {
return ipc::sprintf(ctx, fop(fstr, s), a) >= 0;
} LIBIPC_CATCH(...) {
return false;
}
}
} // namespace
/// \brief The context of fmt.
fmt_context::fmt_context(std::string &j) noexcept
: joined_(j)
, offset_(0) {}
std::size_t fmt_context::capacity() noexcept {
return (offset_ < sbuf_.size()) ? sbuf_.size() : joined_.size();
}
void fmt_context::reset() noexcept {
offset_ = 0;
}
bool fmt_context::finish() noexcept {
LIBIPC_TRY {
if (offset_ < sbuf_.size()) {
joined_.assign(sbuf_.data(), offset_);
} else {
joined_.resize(offset_);
}
return true;
} LIBIPC_CATCH(...) {
return false;
}
}
span<char> fmt_context::buffer(std::size_t sz) noexcept {
auto roundup = [](std::size_t sz) noexcept {
constexpr std::size_t fmt_context_aligned_size = 512U;
return (sz & ~(fmt_context_aligned_size - 1)) + fmt_context_aligned_size;
};
auto sbuf = make_span(sbuf_);
LIBIPC_TRY {
if (offset_ < sbuf.size()) {
if ((offset_ + sz) < sbuf.size()) {
return sbuf.subspan(offset_);
} else {
/// \remark switch the cache to std::string
joined_.assign(sbuf.data(), offset_);
joined_.resize(roundup(offset_ + sz));
}
} else if ((offset_ + sz) >= joined_.size()) {
joined_.resize(roundup(offset_ + sz));
}
return {&joined_[offset_], joined_.size() - offset_};
} LIBIPC_CATCH(...) {
return {};
}
}
void fmt_context::expend(std::size_t sz) noexcept {
offset_ += sz;
}
bool fmt_context::append(span<char const> const &str) noexcept {
auto sz = str.size();
if (sz == 0) return true;
if (str.back() == '\0') --sz;
auto sbuf = buffer(sz);
if (sbuf.size() < sz) {
return false;
}
std::memcpy(sbuf.data(), str.data(), sz);
offset_ += sz;
return true;
}
/// \brief To string conversion.
bool to_string(fmt_context &ctx, char const *a) noexcept {
return to_string(ctx, a, {});
}
bool to_string(fmt_context &ctx, std::string const &a) noexcept {
return ctx.append(a);
}
bool to_string(fmt_context &ctx, char const *a, span<char const> fstr) noexcept {
if (a == nullptr) {
return ipc::sprintf(ctx, fmt_of, fstr, "s", "");
} else {
return ipc::sprintf(ctx, fmt_of, fstr, "s", a);
}
}
bool to_string(fmt_context &ctx, char a) noexcept {
return ipc::sprintf(ctx, fmt_of, {}, "c", a);
}
bool to_string(fmt_context &ctx, wchar_t a) noexcept {
LIBIPC_TRY {
std::string des;
cvt_sstr(std::wstring{a}, des);
return ctx.append(des);
} LIBIPC_CATCH(...) {
return false;
}
}
bool to_string(fmt_context &ctx, char16_t a) noexcept {
LIBIPC_TRY {
std::string des;
cvt_sstr(std::u16string{a}, des);
return ctx.append(des);
} LIBIPC_CATCH(...) {
return false;
}
}
bool to_string(fmt_context &ctx, char32_t a) noexcept {
LIBIPC_TRY {
std::string des;
cvt_sstr(std::u32string{a}, des);
return ctx.append(des);
} LIBIPC_CATCH(...) {
return false;
}
}
bool to_string(fmt_context &ctx, signed short a, span<char const> fstr) noexcept {
return ipc::sprintf(ctx, fmt_of_signed, fstr, "h", a);
}
bool to_string(fmt_context &ctx, unsigned short a, span<char const> fstr) noexcept {
return ipc::sprintf(ctx, fmt_of_unsigned, fstr, "h", a);
}
bool to_string(fmt_context &ctx, signed int a, span<char const> fstr) noexcept {
return ipc::sprintf(ctx, fmt_of_signed, fstr, "", a);
}
bool to_string(fmt_context &ctx, unsigned int a, span<char const> fstr) noexcept {
return ipc::sprintf(ctx, fmt_of_unsigned, fstr, "", a);
}
bool to_string(fmt_context &ctx, signed long a, span<char const> fstr) noexcept {
return ipc::sprintf(ctx, fmt_of_signed, fstr, "l", a);
}
bool to_string(fmt_context &ctx, unsigned long a, span<char const> fstr) noexcept {
return ipc::sprintf(ctx, fmt_of_unsigned, fstr, "l", a);
}
bool to_string(fmt_context &ctx, signed long long a, span<char const> fstr) noexcept {
return ipc::sprintf(ctx, fmt_of_signed, fstr, "ll", a);
}
bool to_string(fmt_context &ctx, unsigned long long a, span<char const> fstr) noexcept {
return ipc::sprintf(ctx, fmt_of_unsigned, fstr, "ll", a);
}
bool to_string(fmt_context &ctx, double a, span<char const> fstr) noexcept {
return ipc::sprintf(ctx, fmt_of_float, fstr, "", a);
}
bool to_string(fmt_context &ctx, long double a, span<char const> fstr) noexcept {
return ipc::sprintf(ctx, fmt_of_float, fstr, "L", a);
}
bool to_string(fmt_context &ctx, std::nullptr_t) noexcept {
return ctx.append("null");
}
bool to_string(fmt_context &ctx, void const volatile *a) noexcept {
if (a == nullptr) {
return to_string(ctx, nullptr);
}
return ipc::sprintf(ctx, fmt_of, "", "p", a);
}
bool to_string(fmt_context &ctx, std::tm const &a, span<char const> fstr) noexcept {
if (fstr.empty()) {
fstr = "%Y-%m-%d %H:%M:%S";
}
LIBIPC_TRY {
std::ostringstream ss;
ss << std::put_time(&a, as_cstr(fstr));
return ctx.append(ss.str());
} LIBIPC_CATCH(...) {
return {};
}
}
} // namespace ipc

View File

@ -0,0 +1,7 @@
#include "libipc/imp/detect_plat.h"
#if defined(LIBIPC_CC_GNUC)
# include "libipc/platform/gnuc/demangle.h"
#else
# include "libipc/platform/win/demangle.h"
#endif

View File

@ -0,0 +1,7 @@
#include "libipc/imp/detect_plat.h"
#if defined(LIBIPC_OS_WIN)
# include "libipc/platform/win/system.h"
#else
# include "libipc/platform/posix/system.h"
#endif

View File

@ -14,7 +14,6 @@
#include "libipc/ipc.h"
#include "libipc/def.h"
#include "libipc/shm.h"
#include "libipc/pool_alloc.h"
#include "libipc/queue.h"
#include "libipc/policy.h"
#include "libipc/rw_lock.h"
@ -25,7 +24,8 @@
#include "libipc/utility/scope_guard.h"
#include "libipc/utility/utility.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
#include "libipc/mem/new.h"
#include "libipc/platform/detail.h"
#include "libipc/circ/elem_array.h"
@ -64,25 +64,30 @@ struct msg_t : msg_t<0, AlignSize> {
};
template <typename T>
ipc::buff_t make_cache(T& data, std::size_t size) {
auto ptr = ipc::mem::alloc(size);
ipc::buff_t make_cache(T &data, std::size_t size) {
auto *ptr = ipc::mem::$new<void>(size);
std::memcpy(ptr, &data, (ipc::detail::min)(sizeof(data), size));
return { ptr, size, ipc::mem::free };
return {
ptr, size,
[](void *p, std::size_t) noexcept {
ipc::mem::$delete(p);
}
};
}
acc_t *cc_acc(ipc::string const &pref) {
static ipc::unordered_map<ipc::string, ipc::shm::handle> handles;
acc_t *cc_acc(std::string const &pref) {
static auto *phs = new ipc::unordered_map<std::string, ipc::shm::handle>; // no delete
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__"})};
auto it = phs->find(pref);
if (it == phs->end()) {
std::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;
it = phs->emplace(pref, std::move(h)).first;
}
return static_cast<acc_t *>(it->second.get());
}
@ -105,8 +110,8 @@ struct cache_t {
struct conn_info_head {
ipc::string prefix_;
ipc::string name_;
std::string prefix_;
std::string name_;
msg_id_t cc_id_; // connection-info id
ipc::detail::waiter cc_waiter_, wt_waiter_, rd_waiter_;
ipc::shm::handle acc_h_;
@ -117,10 +122,10 @@ struct conn_info_head {
, 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_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;
}
@ -146,10 +151,10 @@ struct conn_info_head {
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());
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() {
@ -208,10 +213,10 @@ struct chunk_info_t {
auto& chunk_storages() {
class chunk_handle_t {
ipc::unordered_map<ipc::string, ipc::shm::handle> handles_;
ipc::unordered_map<std::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) {
static bool make_handle(ipc::shm::handle &h, std::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) )) {
@ -223,8 +228,8 @@ auto& chunk_storages() {
public:
chunk_info_t *get_info(conn_info_head *inf, std::size_t chunk_size) {
ipc::string pref {(inf == nullptr) ? ipc::string{} : inf->prefix_};
ipc::string shm_name {ipc::make_prefix(pref, {"CHUNK_INFO__", ipc::to_string(chunk_size)})};
std::string pref {(inf == nullptr) ? std::string{} : inf->prefix_};
std::string shm_name {ipc::make_prefix(pref, "CHUNK_INFO__", chunk_size)};
ipc::shm::handle *h;
{
std::lock_guard<std::mutex> guard {lock_};
@ -243,8 +248,8 @@ auto& chunk_storages() {
};
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;
static auto *chunk_hs = new ipc::map<std::size_t, chunk_handle_ptr_t>; // no delete
return *chunk_hs;
}
chunk_info_t *chunk_storage_info(conn_info_head *inf, std::size_t chunk_size) {
@ -252,15 +257,15 @@ chunk_info_t *chunk_storage_info(conn_info_head *inf, std::size_t chunk_size) {
std::decay_t<decltype(storages)>::iterator it;
{
static ipc::rw_lock lock;
IPC_UNUSED_ std::shared_lock<ipc::rw_lock> guard {lock};
LIBIPC_UNUSED std::shared_lock<ipc::rw_lock> guard {lock};
if ((it = storages.find(chunk_size)) == storages.end()) {
using chunk_handle_ptr_t = std::decay_t<decltype(storages)>::value_type::second_type;
using chunk_handle_t = chunk_handle_ptr_t::element_type;
guard.unlock();
IPC_UNUSED_ std::lock_guard<ipc::rw_lock> guard {lock};
LIBIPC_UNUSED std::lock_guard<ipc::rw_lock> guard {lock};
it = storages.emplace(chunk_size, chunk_handle_ptr_t{
ipc::mem::alloc<chunk_handle_t>(), [](chunk_handle_t *p) {
ipc::mem::destruct(p);
ipc::mem::$new<chunk_handle_t>(), [](chunk_handle_t *p) {
ipc::mem::$delete(p);
}}).first;
}
}
@ -394,11 +399,11 @@ struct queue_generator {
void init() {
conn_info_head::init();
if (!que_.valid()) {
que_.open(ipc::make_prefix(prefix_, {
que_.open(ipc::make_prefix(prefix_,
"QU_CONN__",
this->name_,
"__", ipc::to_string(DataSize),
"__", ipc::to_string(AlignSize)}).c_str());
"__", DataSize,
"__", AlignSize).c_str());
}
}
@ -408,11 +413,11 @@ struct queue_generator {
}
static void clear_storage(char const * prefix, char const * name) noexcept {
queue_t::clear_storage(ipc::make_prefix(ipc::make_string(prefix), {
queue_t::clear_storage(ipc::make_prefix(prefix,
"QU_CONN__",
ipc::make_string(name),
"__", ipc::to_string(DataSize),
"__", ipc::to_string(AlignSize)}).c_str());
name,
"__", DataSize,
"__", AlignSize).c_str());
conn_info_head::clear_storage(prefix, name);
}
@ -447,7 +452,7 @@ constexpr static queue_t* queue_of(ipc::handle_t h) noexcept {
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);
*ph = ipc::mem::$new<conn_info_t>(pref.str, name);
}
return reconnect(ph, start_to_recv);
}
@ -490,7 +495,7 @@ static bool reconnect(ipc::handle_t * ph, bool start_to_recv) {
}
static void destroy(ipc::handle_t h) noexcept {
ipc::mem::free(info_of(h));
ipc::mem::$delete(info_of(h));
}
static std::size_t recv_count(ipc::handle_t h) noexcept {
@ -627,7 +632,10 @@ static ipc::buff_t recv(ipc::handle_t h, std::uint64_t tm) {
for (;;) {
// pop a new message
typename queue_t::value_t msg {};
if (!wait_for(inf->rd_waiter_, [que, &msg] {
if (!wait_for(inf->rd_waiter_, [que, &msg, &h] {
if (!que->connected()) {
reconnect(&h, true);
}
return !que->pop(msg);
}, tm)) {
// pop failed, just return.
@ -654,20 +662,20 @@ static ipc::buff_t recv(ipc::handle_t h, std::uint64_t tm) {
conn_info_t * inf;
ipc::circ::cc_t curr_conns;
ipc::circ::cc_t conn_id;
} *r_info = ipc::mem::alloc<recycle_t>(recycle_t{
} *r_info = ipc::mem::$new<recycle_t>(recycle_t{
buf_id,
inf,
que->elems()->connections(std::memory_order_relaxed),
que->connected_id()
});
if (r_info == nullptr) {
ipc::log("fail: ipc::mem::alloc<recycle_t>.\n");
ipc::log("fail: ipc::mem::$new<recycle_t>.\n");
return ipc::buff_t{buf, msg_size}; // no recycle
} else {
return ipc::buff_t{buf, msg_size, [](void* p_info, std::size_t size) {
auto r_info = static_cast<recycle_t *>(p_info);
IPC_UNUSED_ auto finally = ipc::guard([r_info] {
ipc::mem::free(r_info);
LIBIPC_UNUSED auto finally = ipc::guard([r_info] {
ipc::mem::$delete(r_info);
});
recycle_storage<flag_t>(r_info->storage_id,
r_info->inf,

View File

@ -0,0 +1,53 @@
#include <algorithm> // std::swap
#include "libipc/imp/log.h"
#include "libipc/mem/bytes_allocator.h"
#include "libipc/mem/memory_resource.h"
namespace ipc {
namespace mem {
bytes_allocator::holder_mr_base &bytes_allocator::get_holder() noexcept {
return *reinterpret_cast<holder_mr_base *>(holder_.data());
}
bytes_allocator::holder_mr_base const &bytes_allocator::get_holder() const noexcept {
return *reinterpret_cast<holder_mr_base const *>(holder_.data());
}
void bytes_allocator::init_default_resource() noexcept {
std::ignore = ipc::construct<holder_mr<new_delete_resource>>(holder_.data(), new_delete_resource::get());
}
bytes_allocator::bytes_allocator() noexcept
: bytes_allocator(new_delete_resource::get()) {}
bytes_allocator::~bytes_allocator() noexcept {
ipc::destroy(&get_holder());
}
void bytes_allocator::swap(bytes_allocator &other) noexcept {
std::swap(this->holder_, other.holder_);
}
void *bytes_allocator::allocate(std::size_t s, std::size_t a) const {
LIBIPC_LOG();
if ((a & (a - 1)) != 0) {
log.error("failed: allocate alignment is not a power of 2.");
return nullptr;
}
return get_holder().alloc(s, a);
}
void bytes_allocator::deallocate(void *p, std::size_t s, std::size_t a) const {
LIBIPC_LOG();
if ((a & (a - 1)) != 0) {
log.error("failed: allocate alignment is not a power of 2.");
return;
}
get_holder().dealloc(p, s, a);
}
} // namespace mem
} // namespace ipc

View File

@ -0,0 +1,47 @@
#include <mutex>
#include <array>
#include <cstddef>
#include "libipc/def.h"
#include "libipc/imp/detect_plat.h"
#include "libipc/imp/byte.h"
#include "libipc/mem/bytes_allocator.h"
#include "libipc/mem/memory_resource.h"
namespace ipc {
namespace mem {
class thread_safe_resource : public monotonic_buffer_resource {
public:
thread_safe_resource(span<byte> buffer) noexcept
: monotonic_buffer_resource(buffer) {}
~thread_safe_resource() noexcept {
LIBIPC_UNUSED std::lock_guard<std::mutex> lock(mutex_);
monotonic_buffer_resource::release();
}
void *allocate(std::size_t bytes, std::size_t alignment) noexcept {
LIBIPC_UNUSED std::lock_guard<std::mutex> lock(mutex_);
return monotonic_buffer_resource::allocate(bytes, alignment);
}
void deallocate(void *p, std::size_t bytes, std::size_t alignment) noexcept {
LIBIPC_UNUSED std::lock_guard<std::mutex> lock(mutex_);
monotonic_buffer_resource::deallocate(p, bytes, alignment);
}
private:
std::mutex mutex_;
};
bytes_allocator &central_cache_allocator() noexcept {
static std::array<byte, central_cache_default_size> buf;
static thread_safe_resource res(buf);
static bytes_allocator a(&res);
return a;
}
} // namespace mem
} // namespace ipc

View File

@ -0,0 +1,140 @@
#include <utility>
#include <memory>
#include <algorithm>
#include "libipc/imp/log.h"
#include "libipc/imp/aligned.h"
#include "libipc/imp/detect_plat.h"
#include "libipc/mem/memory_resource.h"
namespace ipc {
namespace mem {
namespace {
template <typename Node>
Node *make_node(bytes_allocator const &upstream, std::size_t initial_size, std::size_t alignment) noexcept {
LIBIPC_LOG();
auto sz = ipc::round_up(sizeof(Node), alignment) + initial_size;
LIBIPC_TRY {
auto *node = static_cast<Node *>(upstream.allocate(sz));
if (node == nullptr) {
log.error("failed: allocate memory for `monotonic_buffer_resource`'s node.",
" bytes = ", initial_size, ", alignment = ", alignment);
return nullptr;
}
node->next = nullptr;
node->size = sz;
return node;
} LIBIPC_CATCH(...) {
log.error("failed: allocate memory for `monotonic_buffer_resource`'s node.",
" bytes = ", initial_size, ", alignment = ", alignment,
"\n\texception: ", ipc::log::exception_string(std::current_exception()));
return nullptr;
}
}
std::size_t next_buffer_size(std::size_t size) noexcept {
return size * 3 / 2;
}
} // namespace
monotonic_buffer_resource::monotonic_buffer_resource() noexcept
: monotonic_buffer_resource(bytes_allocator{}) {}
monotonic_buffer_resource::monotonic_buffer_resource(bytes_allocator upstream) noexcept
: monotonic_buffer_resource(0, std::move(upstream)) {}
monotonic_buffer_resource::monotonic_buffer_resource(std::size_t initial_size) noexcept
: monotonic_buffer_resource(initial_size, bytes_allocator{}) {}
monotonic_buffer_resource::monotonic_buffer_resource(std::size_t initial_size, bytes_allocator upstream) noexcept
: upstream_ (std::move(upstream))
, free_list_ (nullptr)
, head_ (nullptr)
, tail_ (nullptr)
, next_size_ (initial_size)
, initial_buffer_(nullptr)
, initial_size_ (initial_size) {}
monotonic_buffer_resource::monotonic_buffer_resource(ipc::span<ipc::byte> buffer) noexcept
: monotonic_buffer_resource(buffer, bytes_allocator{}) {}
monotonic_buffer_resource::monotonic_buffer_resource(ipc::span<ipc::byte> buffer, bytes_allocator upstream) noexcept
: upstream_ (std::move(upstream))
, free_list_ (nullptr)
, head_ (buffer.begin())
, tail_ (buffer.end())
, next_size_ (next_buffer_size(buffer.size()))
, initial_buffer_(buffer.begin())
, initial_size_ (buffer.size()) {}
monotonic_buffer_resource::~monotonic_buffer_resource() noexcept {
release();
}
bytes_allocator monotonic_buffer_resource::upstream_resource() const noexcept {
return upstream_;
}
void monotonic_buffer_resource::release() noexcept {
LIBIPC_LOG();
LIBIPC_TRY {
while (free_list_ != nullptr) {
auto *next = free_list_->next;
upstream_.deallocate(free_list_, free_list_->size);
free_list_ = next;
}
} LIBIPC_CATCH(...) {
log.error("failed: deallocate memory for `monotonic_buffer_resource`.",
"\n\texception: ", ipc::log::exception_string(std::current_exception()));
}
// reset to initial state at contruction
if ((head_ = initial_buffer_) != nullptr) {
tail_ = head_ + initial_size_;
next_size_ = next_buffer_size(initial_size_);
} else {
tail_ = nullptr;
next_size_ = initial_size_;
}
}
void *monotonic_buffer_resource::allocate(std::size_t bytes, std::size_t alignment) noexcept {
LIBIPC_LOG();
if (bytes == 0) {
log.error("failed: allocate bytes = 0.");
return nullptr;
}
void *p = head_;
auto s = static_cast<std::size_t>(tail_ - head_);
if (std::align(alignment, bytes, p, s) == nullptr) {
next_size_ = (std::max)(next_size_, bytes);
auto *node = make_node<monotonic_buffer_resource::node>(upstream_, next_size_, alignment);
if (node == nullptr) return nullptr;
node->next = free_list_;
free_list_ = node;
next_size_ = next_buffer_size(next_size_);
// try again
s = node->size - sizeof(monotonic_buffer_resource::node);
p = std::align(alignment, bytes, (p = node + 1), s);
if (p == nullptr) {
log.error("failed: allocate memory for `monotonic_buffer_resource`.",
" bytes = ", bytes, ", alignment = ", alignment);
return nullptr;
}
tail_ = static_cast<ipc::byte *>(p) + s;
}
head_ = static_cast<ipc::byte *>(p) + bytes;
return p;
}
void monotonic_buffer_resource::deallocate(void *p, std::size_t bytes, std::size_t alignment) noexcept {
static_cast<void>(p);
static_cast<void>(bytes);
static_cast<void>(alignment);
// Do nothing.
}
} // namespace mem
} // namespace ipc

121
src/libipc/mem/new.cpp Normal file
View File

@ -0,0 +1,121 @@
#include "libipc/mem/new.h"
namespace ipc {
namespace mem {
/// \brief Select the incremental level based on the size.
constexpr inline std::size_t regular_level(std::size_t s) noexcept {
return (s <= 128 ) ? 0 :
(s <= 1024 ) ? 1 :
(s <= 8192 ) ? 2 :
(s <= 65536) ? 3 : 4;
}
/// \brief Use block pools to handle memory less than 64K.
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
class block_resource_base : public block_pool<BlockSize, BlockPoolExpansion>
, public block_collector {
public:
void *allocate(std::size_t /*bytes*/) noexcept override {
return block_pool<BlockSize, BlockPoolExpansion>::allocate();
}
void deallocate(void *p, std::size_t /*bytes*/) noexcept override {
block_pool<BlockSize, BlockPoolExpansion>::deallocate(p);
}
};
/// \brief Use `new`/`delete` to handle memory larger than 64K.
template <>
class block_resource_base<0, 0> : public new_delete_resource
, public block_collector {
public:
void *allocate(std::size_t bytes) noexcept override {
return new_delete_resource::allocate(bytes);
}
void deallocate(void *p, std::size_t bytes) noexcept override {
new_delete_resource::deallocate(p, bytes);
}
};
/// \brief Defines block pool memory resource based on block pool.
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
class block_pool_resource : public block_resource_base<BlockSize, BlockPoolExpansion> {
public:
static block_collector &get() noexcept {
thread_local block_pool_resource instance;
return instance;
}
};
/// \brief Matches the appropriate memory block resource based on a specified size.
block_collector &get_regular_resource(std::size_t s) noexcept {
std::size_t l = regular_level(s);
switch (l) {
case 0:
switch (round_up<std::size_t>(s, 16)) {
case 16 : return block_pool_resource<16 , 512>::get();
case 32 : return block_pool_resource<32 , 512>::get();
case 48 : return block_pool_resource<48 , 512>::get();
case 64 : return block_pool_resource<64 , 512>::get();
case 80 : return block_pool_resource<80 , 512>::get();
case 96 : return block_pool_resource<96 , 512>::get();
case 112: return block_pool_resource<112, 512>::get();
case 128: return block_pool_resource<128, 512>::get();
default : break;
}
break;
case 1:
switch (round_up<std::size_t>(s, 128)) {
case 256 : return block_pool_resource<256 , 256>::get();
case 384 : return block_pool_resource<384 , 256>::get();
case 512 : return block_pool_resource<512 , 256>::get();
case 640 : return block_pool_resource<640 , 256>::get();
case 768 : return block_pool_resource<768 , 256>::get();
case 896 : return block_pool_resource<896 , 256>::get();
case 1024: return block_pool_resource<1024, 256>::get();
default : break;
}
break;
case 2:
switch (round_up<std::size_t>(s, 1024)) {
case 2048: return block_pool_resource<2048, 128>::get();
case 3072: return block_pool_resource<3072, 128>::get();
case 4096: return block_pool_resource<4096, 128>::get();
case 5120: return block_pool_resource<5120, 128>::get();
case 6144: return block_pool_resource<6144, 128>::get();
case 7168: return block_pool_resource<7168, 128>::get();
case 8192: return block_pool_resource<8192, 128>::get();
default : break;
}
break;
case 3:
switch (round_up<std::size_t>(s, 8192)) {
case 16384: return block_pool_resource<16384, 64>::get();
case 24576: return block_pool_resource<24576, 64>::get();
case 32768: return block_pool_resource<32768, 64>::get();
case 40960: return block_pool_resource<40960, 64>::get();
case 49152: return block_pool_resource<49152, 64>::get();
case 57344: return block_pool_resource<57344, 64>::get();
case 65536: return block_pool_resource<65536, 64>::get();
default : break;
}
break;
default:
break;
}
return block_pool_resource<0, 0>::get();
}
void *alloc(std::size_t bytes) noexcept {
return get_regular_resource(bytes).allocate(bytes);
}
void free(void *p, std::size_t bytes) noexcept {
return get_regular_resource(bytes).deallocate(p, bytes);
}
} // namespace mem
} // namespace ipc

View File

@ -0,0 +1,101 @@
#include <cstdlib> // std::aligned_alloc
#include "libipc/imp/detect_plat.h"
#include "libipc/imp/aligned.h"
#include "libipc/imp/system.h"
#include "libipc/imp/log.h"
#include "libipc/mem/memory_resource.h"
#include "libipc/mem/verify_args.h"
namespace ipc {
namespace mem {
/**
* \brief Returns a pointer to a new_delete_resource.
*
* \return new_delete_resource*
*/
new_delete_resource *new_delete_resource::get() noexcept {
static new_delete_resource mem_res;
return &mem_res;
}
/**
* \brief Allocates storage with a size of at least bytes bytes, aligned to the specified alignment.
* Alignment shall be a power of two.
*
* \see https://en.cppreference.com/w/cpp/memory/memory_resource/do_allocate
* https://www.cppstories.com/2019/08/newnew-align/
*
* \return void * - nullptr if storage of the requested size and alignment cannot be obtained.
*/
void *new_delete_resource::allocate(std::size_t bytes, std::size_t alignment) noexcept {
LIBIPC_LOG();
if (!verify_args(bytes, alignment)) {
log.error("invalid bytes = ", bytes, ", alignment = ", alignment);
return nullptr;
}
#if defined(LIBIPC_CPP_17)
/// \see https://en.cppreference.com/w/cpp/memory/c/aligned_alloc
/// \remark The size parameter must be an integral multiple of alignment.
return std::aligned_alloc(alignment, ipc::round_up(bytes, alignment));
#else
if (alignment <= alignof(std::max_align_t)) {
/// \see https://en.cppreference.com/w/cpp/memory/c/malloc
return std::malloc(bytes);
}
#if defined(LIBIPC_OS_WIN)
/// \see https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-malloc
return ::_aligned_malloc(bytes, alignment);
#else // try posix
/// \see https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_memalign.html
void *p = nullptr;
int ret = ::posix_memalign(&p, alignment, bytes);
if (ret != 0) {
log.error("failed: posix_memalign(alignment = ", alignment,
", bytes = ", bytes,
"). error = ", sys::error(ret));
return nullptr;
}
return p;
#endif
#endif // defined(LIBIPC_CPP_17)
}
/**
* \brief Deallocates the storage pointed to by p.
* The storage it points to must not yet have been deallocated, otherwise the behavior is undefined.
*
* \see https://en.cppreference.com/w/cpp/memory/memory_resource/do_deallocate
*
* \param p must have been returned by a prior call to new_delete_resource::do_allocate(bytes, alignment).
*/
void new_delete_resource::deallocate(void *p, std::size_t bytes, std::size_t alignment) noexcept {
LIBIPC_LOG();
if (p == nullptr) {
return;
}
if (!verify_args(bytes, alignment)) {
log.error("invalid bytes = ", bytes, ", alignment = ", alignment);
return;
}
#if defined(LIBIPC_CPP_17)
/// \see https://en.cppreference.com/w/cpp/memory/c/free
std::free(p);
#else
if (alignment <= alignof(std::max_align_t)) {
std::free(p);
return;
}
#if defined(LIBIPC_OS_WIN)
/// \see https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-free
::_aligned_free(p);
#else // try posix
::free(p);
#endif
#endif // defined(LIBIPC_CPP_17)
}
} // namespace mem
} // namespace ipc

39
src/libipc/mem/resource.h Executable file
View File

@ -0,0 +1,39 @@
#pragma once
#include <unordered_map>
#include <map>
#include <string>
#include "libipc/def.h"
#include "libipc/imp/fmt.h"
#include "libipc/mem/container_allocator.h"
namespace ipc {
template <typename Key, typename T>
using unordered_map = std::unordered_map<
Key, T, std::hash<Key>, std::equal_to<Key>, ipc::mem::container_allocator<std::pair<Key const, T>>
>;
template <typename Key, typename T>
using map = std::map<
Key, T, std::less<Key>, ipc::mem::container_allocator<std::pair<Key const, T>>
>;
/// \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 std::string make_string(char const *str) {
return is_valid_string(str) ? std::string{str} : std::string{};
}
/// \brief Combine prefix from a list of strings.
template <typename A1, typename... A>
inline std::string make_prefix(A1 &&prefix, A &&...args) {
return ipc::fmt(std::forward<A1>(prefix), "__IPC_SHM__", std::forward<A>(args)...);
}
} // namespace ipc

View File

@ -0,0 +1,16 @@
#pragma once
#include <cstddef>
namespace ipc {
namespace mem {
/**
* \brief Check that bytes is not 0 and that the alignment is a power of two.
*/
inline constexpr bool verify_args(std::size_t bytes, std::size_t alignment) noexcept {
return (bytes > 0) && (alignment > 0) && ((alignment & (alignment - 1)) == 0);
}
} // namespace mem
} // namespace ipc

View File

@ -1,424 +0,0 @@
#pragma once
#include <algorithm>
#include <utility>
#include <iterator>
#include <limits> // std::numeric_limits
#include <cstdlib>
#include <cassert> // assert
#include "libipc/def.h"
#include "libipc/rw_lock.h"
#include "libipc/utility/concept.h"
#include "libipc/memory/allocator_wrapper.h"
#include "libipc/platform/detail.h"
namespace ipc {
namespace mem {
class static_alloc {
public:
static void swap(static_alloc&) noexcept {}
static void* alloc(std::size_t size) noexcept {
return size ? std::malloc(size) : nullptr;
}
static void free(void* p) noexcept {
std::free(p);
}
static void free(void* p, std::size_t /*size*/) noexcept {
free(p);
}
};
////////////////////////////////////////////////////////////////
/// Scope allocation -- The destructor will release all allocated blocks.
////////////////////////////////////////////////////////////////
namespace detail {
constexpr std::size_t aligned(std::size_t size, size_t alignment) noexcept {
return ( (size - 1) & ~(alignment - 1) ) + alignment;
}
IPC_CONCEPT_(has_take, take(std::move(std::declval<Type>())));
class scope_alloc_base {
protected:
struct block_t {
std::size_t size_;
block_t * next_;
} * head_ = nullptr, * tail_ = nullptr;
enum : std::size_t {
aligned_block_size = aligned(sizeof(block_t), alignof(std::max_align_t))
};
public:
void swap(scope_alloc_base & rhs) {
std::swap(head_, rhs.head_);
std::swap(tail_, rhs.tail_);
}
bool empty() const noexcept {
return head_ == nullptr;
}
void take(scope_alloc_base && rhs) {
if (rhs.empty()) return;
if (empty()) swap(rhs);
else {
std::swap(tail_->next_, rhs.head_);
// rhs.head_ should be nullptr here
tail_ = rhs.tail_;
rhs.tail_ = nullptr;
}
}
void free(void* /*p*/) {}
void free(void* /*p*/, std::size_t) {}
};
} // namespace detail
template <typename AllocP = static_alloc>
class scope_alloc : public detail::scope_alloc_base {
public:
using base_t = detail::scope_alloc_base;
using alloc_policy = AllocP;
private:
alloc_policy alloc_;
void free_all() {
while (!empty()) {
auto curr = head_;
head_ = head_->next_;
alloc_.free(curr, curr->size_);
}
// now head_ is nullptr
}
public:
scope_alloc() = default;
scope_alloc(scope_alloc && rhs) { swap(rhs); }
scope_alloc& operator=(scope_alloc rhs) { swap(rhs); return (*this); }
~scope_alloc() { free_all(); }
void swap(scope_alloc& rhs) {
alloc_.swap(rhs.alloc_);
base_t::swap(rhs);
}
template <typename A = AllocP>
auto take(scope_alloc && rhs) -> ipc::require<detail::has_take<A>::value> {
base_t::take(std::move(rhs));
alloc_.take(std::move(rhs.alloc_));
}
template <typename A = AllocP>
auto take(scope_alloc && rhs) -> ipc::require<!detail::has_take<A>::value> {
base_t::take(std::move(rhs));
}
void* alloc(std::size_t size) {
std::size_t real_size = aligned_block_size + size;
auto curr = static_cast<block_t*>(alloc_.alloc(real_size));
curr->size_ = real_size;
curr->next_ = head_;
head_ = curr;
if (tail_ == nullptr) {
tail_ = curr;
}
return (reinterpret_cast<byte_t*>(curr) + aligned_block_size);
}
};
////////////////////////////////////////////////////////////////
/// Fixed-size blocks allocation
////////////////////////////////////////////////////////////////
namespace detail {
class fixed_alloc_base {
protected:
std::size_t block_size_;
std::size_t init_expand_;
void * cursor_;
void init(std::size_t block_size, std::size_t init_expand) {
block_size_ = block_size;
init_expand_ = init_expand;
cursor_ = nullptr;
}
static void** node_p(void* node) {
return reinterpret_cast<void**>(node);
}
static auto& next(void* node) {
return *node_p(node);
}
public:
bool operator<(fixed_alloc_base const & right) const {
return init_expand_ < right.init_expand_;
}
void set_block_size(std::size_t block_size) {
block_size_ = block_size;
}
void swap(fixed_alloc_base& rhs) {
std::swap(block_size_ , rhs.block_size_);
std::swap(init_expand_, rhs.init_expand_);
std::swap(cursor_ , rhs.cursor_);
}
bool empty() const noexcept {
return cursor_ == nullptr;
}
void take(fixed_alloc_base && rhs) {
assert(block_size_ == rhs.block_size_);
init_expand_ = (ipc::detail::max)(init_expand_, rhs.init_expand_);
if (rhs.empty()) return;
auto curr = cursor_;
if (curr != nullptr) while (1) {
auto next_cur = next(curr);
if (next_cur == nullptr) {
std::swap(next(curr), rhs.cursor_);
return;
}
// next_cur != nullptr
else curr = next_cur;
}
// curr == nullptr, means cursor_ == nullptr
else std::swap(cursor_, rhs.cursor_);
// rhs.cursor_ must be nullptr
}
void free(void* p) {
if (p == nullptr) return;
next(p) = cursor_;
cursor_ = p;
}
void free(void* p, std::size_t) {
free(p);
}
};
template <typename AllocP, typename ExpandP>
class fixed_alloc : public detail::fixed_alloc_base {
public:
using base_t = detail::fixed_alloc_base;
using alloc_policy = AllocP;
private:
alloc_policy alloc_;
void* try_expand() {
if (empty()) {
auto size = ExpandP::next(block_size_, init_expand_);
auto p = node_p(cursor_ = alloc_.alloc(size));
for (std::size_t i = 0; i < (size / block_size_) - 1; ++i)
p = node_p((*p) = reinterpret_cast<byte_t*>(p) + block_size_);
(*p) = nullptr;
}
return cursor_;
}
public:
explicit fixed_alloc(std::size_t block_size, std::size_t init_expand = 1) {
init(block_size, init_expand);
}
fixed_alloc(fixed_alloc && rhs) {
init(0, 0);
swap(rhs);
}
fixed_alloc& operator=(fixed_alloc rhs) {
swap(rhs);
return (*this);
}
void swap(fixed_alloc& rhs) {
alloc_.swap(rhs.alloc_);
base_t::swap(rhs);
}
template <typename A = AllocP>
auto take(fixed_alloc && rhs) -> ipc::require<detail::has_take<A>::value> {
base_t::take(std::move(rhs));
alloc_.take(std::move(rhs.alloc_));
}
void* alloc() {
void* p = try_expand();
cursor_ = next(p);
return p;
}
void* alloc(std::size_t) {
return alloc();
}
};
} // namespace detail
template <std::size_t BaseSize = sizeof(void*) * 1024,
std::size_t LimitSize = (std::numeric_limits<std::uint32_t>::max)()>
struct fixed_expand_policy {
enum : std::size_t {
base_size = BaseSize,
limit_size = LimitSize
};
constexpr static std::size_t prev(std::size_t e) noexcept {
return ((e / 2) == 0) ? 1 : (e / 2);
}
constexpr static std::size_t next(std::size_t e) noexcept {
return e * 2;
}
static std::size_t next(std::size_t block_size, std::size_t & e) {
auto n = ipc::detail::max<std::size_t>(block_size, base_size) * e;
e = ipc::detail::min<std::size_t>(limit_size, next(e));
return n;
}
};
template <std::size_t BlockSize,
typename AllocP = scope_alloc<>,
typename ExpandP = fixed_expand_policy<>>
class fixed_alloc : public detail::fixed_alloc<AllocP, ExpandP> {
public:
using base_t = detail::fixed_alloc<AllocP, ExpandP>;
enum : std::size_t {
block_size = ipc::detail::max<std::size_t>(BlockSize, sizeof(void*))
};
public:
explicit fixed_alloc(std::size_t init_expand)
: base_t(block_size, init_expand) {
}
fixed_alloc() : fixed_alloc(1) {}
fixed_alloc(fixed_alloc && rhs)
: base_t(std::move(rhs)) {
}
fixed_alloc& operator=(fixed_alloc rhs) {
swap(rhs);
return (*this);
}
void swap(fixed_alloc& rhs) {
base_t::swap(rhs);
}
};
////////////////////////////////////////////////////////////////
/// Variable-size blocks allocation (without alignment)
////////////////////////////////////////////////////////////////
namespace detail {
class variable_alloc_base {
protected:
byte_t * head_ = nullptr, * tail_ = nullptr;
public:
void swap(variable_alloc_base & rhs) {
std::swap(head_, rhs.head_);
std::swap(tail_, rhs.tail_);
}
std::size_t remain() const noexcept {
return static_cast<std::size_t>(tail_ - head_);
}
bool empty() const noexcept {
return remain() == 0;
}
void take(variable_alloc_base && rhs) {
if (remain() < rhs.remain()) {
// replace this by rhs
head_ = rhs.head_;
tail_ = rhs.tail_;
}
// discard rhs
rhs.head_ = rhs.tail_ = nullptr;
}
void free(void* /*p*/) {}
void free(void* /*p*/, std::size_t) {}
};
} // namespace detail
template <std::size_t ChunkSize = (sizeof(void*) * 1024), typename AllocP = scope_alloc<>>
class variable_alloc : public detail::variable_alloc_base {
public:
using base_t = detail::variable_alloc_base;
using alloc_policy = AllocP;
enum : std::size_t {
aligned_chunk_size = detail::aligned(ChunkSize, alignof(std::max_align_t))
};
private:
alloc_policy alloc_;
public:
variable_alloc() = default;
variable_alloc(variable_alloc && rhs) { swap(rhs); }
variable_alloc& operator=(variable_alloc rhs) { swap(rhs); return (*this); }
void swap(variable_alloc& rhs) {
alloc_.swap(rhs.alloc_);
base_t::swap(rhs);
}
template <typename A = AllocP>
auto take(variable_alloc && rhs) -> ipc::require<detail::has_take<A>::value> {
base_t::take(std::move(rhs));
alloc_.take(std::move(rhs.alloc_));
}
void* alloc(std::size_t size) {
/*
* byte alignment is always alignof(std::max_align_t).
*/
size = detail::aligned(size, alignof(std::max_align_t));
void* ptr;
// size would never be 0 here
if (remain() < size) {
std::size_t chunk_size = ipc::detail::max<std::size_t>(aligned_chunk_size, size);
ptr = alloc_.alloc(chunk_size);
tail_ = static_cast<byte_t*>(ptr) + chunk_size;
head_ = tail_ - (chunk_size - size);
}
else {
ptr = head_;
head_ += size;
}
return ptr;
}
};
} // namespace mem
} // namespace ipc

View File

@ -1,121 +0,0 @@
#pragma once
#include <limits> // std::numeric_limits
#include <utility> // std::forward
#include <cstddef>
#include "libipc/pool_alloc.h"
namespace ipc {
namespace mem {
////////////////////////////////////////////////////////////////
/// The allocator wrapper class for STL
////////////////////////////////////////////////////////////////
namespace detail {
template <typename T, typename AllocP>
struct rebind {
template <typename U>
using alloc_t = AllocP;
};
template <typename T, template <typename> class AllocT>
struct rebind<T, AllocT<T>> {
template <typename U>
using alloc_t = AllocT<U>;
};
} // namespace detail
template <typename T, typename AllocP>
class allocator_wrapper {
template <typename U, typename AllocU>
friend class allocator_wrapper;
public:
// type definitions
typedef T value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
typedef AllocP alloc_policy;
private:
alloc_policy alloc_;
public:
allocator_wrapper() noexcept {}
// construct by copying (do nothing)
allocator_wrapper (const allocator_wrapper<T, AllocP>&) noexcept {}
allocator_wrapper& operator=(const allocator_wrapper<T, AllocP>&) noexcept { return *this; }
// construct from a related allocator (do nothing)
template <typename U, typename AllocU> allocator_wrapper (const allocator_wrapper<U, AllocU>&) noexcept {}
template <typename U, typename AllocU> allocator_wrapper& operator=(const allocator_wrapper<U, AllocU>&) noexcept { return *this; }
allocator_wrapper (allocator_wrapper && rhs) noexcept : alloc_ ( std::move(rhs.alloc_) ) {}
allocator_wrapper& operator=(allocator_wrapper && rhs) noexcept { alloc_ = std::move(rhs.alloc_); return *this; }
public:
// the other type of std_allocator
template <typename U>
struct rebind {
using other = allocator_wrapper< U, typename detail::rebind<T, AllocP>::template alloc_t<U> >;
};
constexpr size_type max_size(void) const noexcept {
return (std::numeric_limits<size_type>::max)() / sizeof(value_type);
}
public:
pointer allocate(size_type count) noexcept {
if (count == 0) return nullptr;
if (count > this->max_size()) return nullptr;
return static_cast<pointer>(alloc_.alloc(count * sizeof(value_type)));
}
void deallocate(pointer p, size_type count) noexcept {
alloc_.free(p, count * sizeof(value_type));
}
template <typename... P>
static void construct(pointer p, P && ... params) {
ipc::mem::construct(p, std::forward<P>(params)...);
}
static void destroy(pointer p) {
ipc::mem::destruct(p);
}
};
template <class AllocP>
class allocator_wrapper<void, AllocP> {
public:
// type definitions
typedef void value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
typedef AllocP alloc_policy;
};
template <typename T, typename U, class AllocP>
constexpr bool operator==(const allocator_wrapper<T, AllocP>&, const allocator_wrapper<U, AllocP>&) noexcept {
return true;
}
template <typename T, typename U, class AllocP>
constexpr bool operator!=(const allocator_wrapper<T, AllocP>&, const allocator_wrapper<U, AllocP>&) noexcept {
return false;
}
} // namespace mem
} // namespace ipc

View File

@ -1,110 +0,0 @@
#pragma once
#include <type_traits>
#include <limits>
#include <utility>
#include <functional>
#include <unordered_map>
#include <map>
#include <string>
#include <cstdio>
#include "libipc/def.h"
#include "libipc/memory/alloc.h"
#include "libipc/memory/wrapper.h"
#include "libipc/platform/detail.h"
namespace ipc {
namespace mem {
//using async_pool_alloc = static_wrapper<variable_wrapper<async_wrapper<
// detail::fixed_alloc<
// variable_alloc <sizeof(void*) * 1024 * 256>,
// fixed_expand_policy<sizeof(void*) * 1024, sizeof(void*) * 1024 * 256>
// >,
// default_recycler >>>;
using async_pool_alloc = ipc::mem::static_alloc;
template <typename T>
using allocator = allocator_wrapper<T, async_pool_alloc>;
} // namespace mem
namespace {
constexpr char const * pf(int) { return "%d" ; }
constexpr char const * pf(long) { return "%ld" ; }
constexpr char const * pf(long long) { return "%lld"; }
constexpr char const * pf(unsigned int) { return "%u" ; }
constexpr char const * pf(unsigned long) { return "%lu" ; }
constexpr char const * pf(unsigned long long) { return "%llu"; }
constexpr char const * pf(float) { return "%f" ; }
constexpr char const * pf(double) { return "%f" ; }
constexpr char const * pf(long double) { return "%Lf" ; }
} // internal-linkage
template <typename T>
struct hash : public std::hash<T> {};
template <typename Key, typename T>
using unordered_map = std::unordered_map<
Key, T, ipc::hash<Key>, std::equal_to<Key>, ipc::mem::allocator<std::pair<Key const, T>>
>;
template <typename Key, typename T>
using map = std::map<
Key, T, std::less<Key>, ipc::mem::allocator<std::pair<Key const, T>>
>;
template <typename Char>
using basic_string = std::basic_string<
Char, std::char_traits<Char>, ipc::mem::allocator<Char>
>;
using string = basic_string<char>;
using wstring = basic_string<wchar_t>;
template <> struct hash<string> {
std::size_t operator()(string const &val) const noexcept {
return std::hash<char const *>{}(val.c_str());
}
};
template <> struct hash<wstring> {
std::size_t operator()(wstring const &val) const noexcept {
return std::hash<wchar_t const *>{}(val.c_str());
}
};
template <typename T>
ipc::string to_string(T val) {
char buf[std::numeric_limits<T>::digits10 + 1] {};
if (std::snprintf(buf, sizeof(buf), pf(val), val) > 0) {
return buf;
}
return {};
}
/// \brief Check string validity.
constexpr bool is_valid_string(char const *str) noexcept {
return (str != nullptr) && (str[0] != '\0');
}
/// \brief Make a valid string.
inline ipc::string make_string(char const *str) {
return is_valid_string(str) ? ipc::string{str} : ipc::string{};
}
/// \brief Combine prefix from a list of strings.
inline ipc::string make_prefix(ipc::string prefix, std::initializer_list<ipc::string> args) {
prefix += "__IPC_SHM__";
for (auto const &txt: args) {
if (txt.empty()) continue;
prefix += txt;
}
return prefix;
}
} // namespace ipc

View File

@ -1,327 +0,0 @@
#pragma once
#include <tuple>
#include <thread>
#include <deque> // std::deque
#include <functional> // std::function
#include <utility> // std::forward
#include <cstddef>
#include <cassert> // assert
#include <type_traits> // std::aligned_storage_t
#include "libipc/def.h"
#include "libipc/rw_lock.h"
#include "libipc/pool_alloc.h"
#include "libipc/utility/concept.h"
#include "libipc/memory/alloc.h"
#include "libipc/platform/detail.h"
namespace ipc {
namespace mem {
////////////////////////////////////////////////////////////////
/// Thread-safe allocation wrapper
////////////////////////////////////////////////////////////////
namespace detail {
IPC_CONCEPT_(is_comparable, operator<(std::declval<Type>()));
} // namespace detail
template <typename AllocP, bool = detail::is_comparable<AllocP>::value>
class limited_recycler;
template <typename AllocP>
class limited_recycler<AllocP, true> {
public:
using alloc_policy = AllocP;
protected:
std::deque<alloc_policy> master_allocs_;
ipc::spin_lock master_lock_;
template <typename F>
void take_first_do(F && pred) {
auto it = master_allocs_.begin();
pred(const_cast<alloc_policy&>(*it));
master_allocs_.erase(it);
}
public:
void try_recover(alloc_policy & alc) {
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(master_lock_);
if (master_allocs_.empty()) return;
take_first_do([&alc](alloc_policy & first) { alc.swap(first); });
}
void collect(alloc_policy && alc) {
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(master_lock_);
if (master_allocs_.size() >= 32) {
take_first_do([](alloc_policy &) {}); // erase first
}
master_allocs_.emplace_back(std::move(alc));
}
IPC_CONSTEXPR_ auto try_replenish(alloc_policy&, std::size_t) noexcept {}
};
template <typename AllocP>
class default_recycler : public limited_recycler<AllocP> {
IPC_CONCEPT_(has_remain, remain());
IPC_CONCEPT_(has_empty , empty());
template <typename A>
void try_fill(A & alc) {
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(this->master_lock_);
if (this->master_allocs_.empty()) return;
this->take_first_do([&alc](alloc_policy & first) { alc.take(std::move(first)); });
}
public:
using alloc_policy = typename limited_recycler<AllocP>::alloc_policy;
template <typename A = AllocP>
auto try_replenish(alloc_policy & alc, std::size_t size)
-> ipc::require<detail::has_take<A>::value && has_remain<A>::value> {
if (alc.remain() >= size) return;
this->try_fill(alc);
}
template <typename A = AllocP>
auto try_replenish(alloc_policy & alc, std::size_t /*size*/)
-> ipc::require<detail::has_take<A>::value && !has_remain<A>::value && has_empty<A>::value> {
if (!alc.empty()) return;
this->try_fill(alc);
}
template <typename A = AllocP>
auto try_replenish(alloc_policy & alc, std::size_t /*size*/)
-> ipc::require<!detail::has_take<A>::value && has_empty<A>::value> {
if (!alc.empty()) return;
this->try_recover(alc);
}
template <typename A = AllocP>
IPC_CONSTEXPR_ auto try_replenish(alloc_policy & /*alc*/, std::size_t /*size*/) noexcept
-> ipc::require<(!detail::has_take<A>::value || !has_remain<A>::value) && !has_empty<A>::value> {
// Do Nothing.
}
};
template <typename AllocP>
class empty_recycler {
public:
using alloc_policy = AllocP;
IPC_CONSTEXPR_ void try_recover(alloc_policy&) noexcept {}
IPC_CONSTEXPR_ auto try_replenish(alloc_policy&, std::size_t) noexcept {}
IPC_CONSTEXPR_ void collect(alloc_policy&&) noexcept {}
};
template <typename AllocP,
template <typename> class RecyclerP = default_recycler>
class async_wrapper {
public:
using alloc_policy = AllocP;
private:
RecyclerP<alloc_policy> recycler_;
class alloc_proxy : public AllocP {
async_wrapper * w_ = nullptr;
public:
alloc_proxy(alloc_proxy && rhs) = default;
template <typename ... P>
alloc_proxy(async_wrapper* w, P && ... pars)
: AllocP(std::forward<P>(pars) ...), w_(w) {
assert(w_ != nullptr);
w_->recycler_.try_recover(*this);
}
~alloc_proxy() {
w_->recycler_.collect(std::move(*this));
}
auto alloc(std::size_t size) {
w_->recycler_.try_replenish(*this, size);
return AllocP::alloc(size);
}
};
friend class alloc_proxy;
using ref_t = alloc_proxy&;
std::function<ref_t()> get_alloc_;
public:
template <typename ... P>
async_wrapper(P ... pars) {
get_alloc_ = [this, pars ...]()->ref_t {
thread_local alloc_proxy tls(pars ...);
return tls;
};
}
void* alloc(std::size_t size) {
return get_alloc_().alloc(size);
}
void free(void* p, std::size_t size) {
get_alloc_().free(p, size);
}
};
////////////////////////////////////////////////////////////////
/// Thread-safe allocation wrapper (with spin_lock)
////////////////////////////////////////////////////////////////
template <typename AllocP, typename MutexT = ipc::spin_lock>
class sync_wrapper {
public:
using alloc_policy = AllocP;
using mutex_type = MutexT;
private:
mutex_type lock_;
alloc_policy alloc_;
public:
template <typename ... P>
sync_wrapper(P && ... pars)
: alloc_(std::forward<P>(pars) ...)
{}
void swap(sync_wrapper& rhs) {
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
alloc_.swap(rhs.alloc_);
}
void* alloc(std::size_t size) {
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
return alloc_.alloc(size);
}
void free(void* p, std::size_t size) {
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lock_);
alloc_.free(p, size);
}
};
////////////////////////////////////////////////////////////////
/// Variable memory allocation wrapper
////////////////////////////////////////////////////////////////
template <std::size_t BaseSize = 0, std::size_t IterSize = sizeof(void*)>
struct default_mapping_policy {
enum : std::size_t {
base_size = BaseSize,
iter_size = IterSize,
classes_size = 64
};
template <typename F, typename ... P>
IPC_CONSTEXPR_ static void foreach(F && f, P && ... params) {
for (std::size_t i = 0; i < classes_size; ++i) {
f(i, std::forward<P>(params)...);
}
}
IPC_CONSTEXPR_ static std::size_t block_size(std::size_t id) noexcept {
return (id < classes_size) ? (base_size + (id + 1) * iter_size) : 0;
}
template <typename F, typename D, typename ... P>
IPC_CONSTEXPR_ static auto classify(F && f, D && d, std::size_t size, P && ... params) {
std::size_t id = (size - base_size - 1) / iter_size;
return (id < classes_size) ?
f(id, size, std::forward<P>(params)...) :
d(size, std::forward<P>(params)...);
}
};
template <typename FixedAlloc,
typename DefaultAlloc = mem::static_alloc,
typename MappingP = default_mapping_policy<>>
class variable_wrapper {
struct initiator {
using falc_t = std::aligned_storage_t<sizeof(FixedAlloc), alignof(FixedAlloc)>;
falc_t arr_[MappingP::classes_size];
initiator() {
MappingP::foreach([](std::size_t id, falc_t * a) {
ipc::mem::construct(&initiator::at(a, id), MappingP::block_size(id));
}, arr_);
}
~initiator() {
MappingP::foreach([](std::size_t id, falc_t * a) {
ipc::mem::destruct(&initiator::at(a, id));
}, arr_);
}
static FixedAlloc & at(falc_t * arr, std::size_t id) noexcept {
return reinterpret_cast<FixedAlloc&>(arr[id]);
}
} init_;
using falc_t = typename initiator::falc_t;
public:
void swap(variable_wrapper & other) {
MappingP::foreach([](std::size_t id, falc_t * in, falc_t * ot) {
initiator::at(in, id).swap(initiator::at(ot, id));
}, init_.arr_, other.init_.arr_);
}
void* alloc(std::size_t size) {
return MappingP::classify([](std::size_t id, std::size_t size, falc_t * a) {
return initiator::at(a, id).alloc(size);
}, [](std::size_t size, falc_t *) {
return DefaultAlloc::alloc(size);
}, size, init_.arr_);
}
void free(void* p, std::size_t size) {
MappingP::classify([](std::size_t id, std::size_t size, void* p, falc_t * a) {
initiator::at(a, id).free(p, size);
}, [](std::size_t size, void* p, falc_t *) {
DefaultAlloc::free(p, size);
}, size, p, init_.arr_);
}
};
////////////////////////////////////////////////////////////////
/// Static allocation wrapper
////////////////////////////////////////////////////////////////
template <typename AllocP>
class static_wrapper {
public:
using alloc_policy = AllocP;
static alloc_policy& instance() {
static alloc_policy alloc;
return alloc;
}
static void swap(static_wrapper&) {}
static void* alloc(std::size_t size) {
return instance().alloc(size);
}
static void free(void* p, std::size_t size) {
instance().free(p, size);
}
};
} // namespace mem
} // namespace ipc

View File

@ -1,20 +1,7 @@
#ifndef LIBIPC_SRC_PLATFORM_DETAIL_H_
#define LIBIPC_SRC_PLATFORM_DETAIL_H_
// detect platform
#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || \
defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || \
defined(WINCE) || defined(_WIN32_WCE)
# define IPC_OS_WINDOWS_
#elif defined(__linux__) || defined(__linux)
# define IPC_OS_LINUX_
#elif defined(__QNX__)
# define IPC_OS_QNX_
#elif defined(__APPLE__)
#elif defined(__ANDROID__)
// TBD
#endif
#include "libipc/imp/detect_plat.h"
#if defined(__cplusplus)
@ -27,12 +14,6 @@
// pre-defined
#ifdef IPC_UNUSED_
# error "IPC_UNUSED_ has been defined."
#endif
#ifdef IPC_FALLTHROUGH_
# error "IPC_FALLTHROUGH_ has been defined."
#endif
#ifdef IPC_STBIND_
# error "IPC_STBIND_ has been defined."
#endif
@ -42,23 +23,11 @@
#if __cplusplus >= 201703L
#define IPC_UNUSED_ [[maybe_unused]]
#define IPC_FALLTHROUGH_ [[fallthrough]]
#define IPC_STBIND_(A, B, ...) auto [A, B] = __VA_ARGS__
#define IPC_CONSTEXPR_ constexpr
#else /*__cplusplus < 201703L*/
#if defined(_MSC_VER)
# define IPC_UNUSED_ __pragma(warning(suppress: 4100 4101 4189))
#elif defined(__GNUC__)
# define IPC_UNUSED_ __attribute__((__unused__))
#else
# define IPC_UNUSED_
#endif
#define IPC_FALLTHROUGH_
#define IPC_STBIND_(A, B, ...) \
auto tp___ = __VA_ARGS__ \
auto A = std::get<0>(tp___); \

View File

@ -0,0 +1,43 @@
/**
* \file libipc/platform/gnuc/demangle.h
* \author mutouyun (orz@orzz.org)
*/
#pragma once
#include <cxxabi.h> // abi::__cxa_demangle
#include <cstdlib> // std::malloc
#include "libipc/imp/nameof.h"
#include "libipc/imp/scope_exit.h"
#include "libipc/imp/detect_plat.h"
namespace ipc {
/**
* \brief The conventional way to obtain demangled symbol name.
* \see https://www.boost.org/doc/libs/1_80_0/libs/core/doc/html/core/demangle.html
*
* \param name the mangled name
* \return std::string a human-readable demangled type name
*/
std::string demangle(std::string name) noexcept {
/// \see https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.3/a01696.html
std::size_t sz = name.size() + 1;
char *buffer = static_cast<char *>(std::malloc(sz));
int status = 0;
char *realname = abi::__cxa_demangle(name.data(), buffer, &sz, &status);
if (realname == nullptr) {
std::free(buffer);
return {};
}
LIBIPC_SCOPE_EXIT(guard) = [realname] {
std::free(realname);
};
LIBIPC_TRY {
return std::move(name.assign(realname, sz));
} LIBIPC_CATCH(...) {
return {};
}
}
} // namespace ipc

View File

@ -27,7 +27,7 @@ public:
return false;
}
} else {
auto ts = detail::make_timespec(tm);
auto ts = linux_::detail::make_timespec(tm);
int eno = A0_SYSERR(a0_cnd_timedwait(native(), static_cast<a0_mtx_t *>(mtx.native()), {ts}));
if (eno != 0) {
if (eno != ETIMEDOUT) {

View File

@ -10,6 +10,7 @@
#include "a0/err_macro.h"
namespace ipc {
namespace linux_ {
namespace detail {
inline bool calc_wait_time(timespec &ts, std::uint64_t tm /*ms*/) noexcept {
@ -43,4 +44,5 @@ inline timespec make_timespec(std::uint64_t tm /*ms*/) noexcept(false) {
}
} // namespace detail
} // namespace linux_
} // namespace ipc

View File

@ -7,7 +7,7 @@
#include "libipc/platform/detail.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
#include "libipc/shm.h"
#include "get_wait_time.h"
@ -25,7 +25,7 @@ public:
bool lock(std::uint64_t tm) noexcept {
if (!valid()) return false;
for (;;) {
auto ts = detail::make_timespec(tm);
auto ts = linux_::detail::make_timespec(tm);
int eno = A0_SYSERR(
(tm == invalid_value) ? a0_mtx_lock(native())
: a0_mtx_timedlock(native(), {ts}));
@ -56,7 +56,7 @@ public:
bool try_lock() noexcept(false) {
if (!valid()) return false;
int eno = A0_SYSERR(a0_mtx_timedlock(native(), {detail::make_timespec(0)}));
int eno = A0_SYSERR(a0_mtx_timedlock(native(), {linux_::detail::make_timespec(0)}));
switch (eno) {
case 0:
return true;
@ -108,7 +108,7 @@ class mutex {
shm_data(init arg)
: mtx{}, ref{0} { mtx.open(arg.name); }
};
ipc::map<ipc::string, shm_data> mutex_handles;
ipc::map<std::string, shm_data> mutex_handles;
std::mutex lock;
static curr_prog &get() {
@ -122,7 +122,7 @@ class mutex {
return;
}
auto &info = curr_prog::get();
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
LIBIPC_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
@ -136,10 +136,10 @@ class mutex {
}
template <typename F>
static void release_mutex(ipc::string const &name, F &&clear) {
static void release_mutex(std::string const &name, F &&clear) {
if (name.empty()) return;
auto &info = curr_prog::get();
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
LIBIPC_UNUSED std::lock_guard<std::mutex> guard {info.lock};
auto it = info.mutex_handles.find(name);
if (it == info.mutex_handles.end()) {
return;

View File

@ -1,13 +1,13 @@
#include "libipc/platform/detail.h"
#if defined(IPC_OS_WINDOWS_)
#elif defined(IPC_OS_LINUX_)
#if defined(LIBIPC_OS_WIN)
#elif defined(LIBIPC_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_)
#elif defined(LIBIPC_OS_QNX)
#else/*IPC_OS*/
# error "Unsupported platform."
#endif

View File

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

View File

@ -63,7 +63,7 @@ public:
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); });
LIBIPC_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;
@ -115,7 +115,7 @@ public:
}
break;
default: {
auto ts = detail::make_timespec(tm);
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) {

View File

@ -10,6 +10,7 @@
#include "libipc/utility/log.h"
namespace ipc {
namespace posix_ {
namespace detail {
inline bool calc_wait_time(timespec &ts, std::uint64_t tm /*ms*/) noexcept {
@ -36,4 +37,5 @@ inline timespec make_timespec(std::uint64_t tm /*ms*/) noexcept(false) {
}
} // namespace detail
} // namespace posix_
} // namespace ipc

View File

@ -12,7 +12,7 @@
#include "libipc/platform/detail.h"
#include "libipc/utility/log.h"
#include "libipc/utility/scope_guard.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
#include "libipc/shm.h"
#include "get_wait_time.h"
@ -38,7 +38,7 @@ class mutex {
shm_data(init arg)
: shm{arg.name, arg.size}, ref{0} {}
};
ipc::map<ipc::string, shm_data> mutex_handles;
ipc::map<std::string, shm_data> mutex_handles;
std::mutex lock;
static curr_prog &get() {
@ -52,7 +52,7 @@ class mutex {
return nullptr;
}
auto &info = curr_prog::get();
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
LIBIPC_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
@ -71,10 +71,10 @@ class mutex {
}
template <typename F>
static void release_mutex(ipc::string const &name, F &&clear) {
static void release_mutex(std::string const &name, F &&clear) {
if (name.empty()) return;
auto &info = curr_prog::get();
IPC_UNUSED_ std::lock_guard<std::mutex> guard {info.lock};
LIBIPC_UNUSED std::lock_guard<std::mutex> guard {info.lock};
auto it = info.mutex_handles.find(name);
if (it == info.mutex_handles.end()) {
return;
@ -130,7 +130,7 @@ public:
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); });
LIBIPC_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;
@ -196,7 +196,7 @@ public:
bool lock(std::uint64_t tm) noexcept {
if (!valid()) return false;
for (;;) {
auto ts = detail::make_timespec(tm);
auto ts = posix_::detail::make_timespec(tm);
int eno = (tm == invalid_value)
? ::pthread_mutex_lock(mutex_)
: ::pthread_mutex_timedlock(mutex_, &ts);
@ -230,7 +230,7 @@ public:
bool try_lock() noexcept(false) {
if (!valid()) return false;
auto ts = detail::make_timespec(0);
auto ts = posix_::detail::make_timespec(0);
int eno = ::pthread_mutex_timedlock(mutex_, &ts);
switch (eno) {
case 0:

View File

@ -88,7 +88,7 @@ public:
return false;
}
} else {
auto ts = detail::make_timespec(tm);
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",

View File

@ -13,10 +13,10 @@
#include "libipc/shm.h"
#include "libipc/def.h"
#include "libipc/pool_alloc.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
#include "libipc/mem/new.h"
namespace {
@ -28,7 +28,7 @@ struct id_info_t {
int fd_ = -1;
void* mem_ = nullptr;
std::size_t size_ = 0;
ipc::string name_;
std::string name_;
};
constexpr std::size_t calc_size(std::size_t size) {
@ -51,7 +51,7 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
}
// For portable use, a shared memory object should be identified by name of the form /somename.
// see: https://man7.org/linux/man-pages/man3/shm_open.3.html
ipc::string op_name = ipc::string{"/"} + name;
std::string op_name = std::string{"/"} + name;
// Open the object for read-write access.
int flag = O_RDWR;
switch (mode) {
@ -81,7 +81,7 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
::fchmod(fd, S_IRUSR | S_IWUSR |
S_IRGRP | S_IWGRP |
S_IROTH | S_IWOTH);
auto ii = mem::alloc<id_info_t>();
auto ii = mem::$new<id_info_t>();
ii->fd_ = fd;
ii->size_ = size;
ii->name_ = std::move(op_name);
@ -177,7 +177,7 @@ std::int32_t release(id_t id) noexcept {
}
}
else ::munmap(ii->mem_, ii->size_);
mem::free(ii);
mem::$delete(ii);
return ret;
}

View File

@ -0,0 +1,49 @@
/**
* \file libipc/platform/posix/system.h
* \author mutouyun (orz@orzz.org)
*/
#pragma once
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include "libipc/imp/system.h"
#include "libipc/imp/log.h"
namespace ipc {
namespace sys {
/**
* \brief Get the system error number.
* \see https://en.cppreference.com/w/cpp/error/generic_category
* https://man7.org/linux/man-pages/man3/errno.3.html
*/
std::error_code error() noexcept {
return std::error_code(errno, std::generic_category());
}
/**
* \brief Gets configuration information at run time
* https://man7.org/linux/man-pages/man2/getpagesize.2.html
* https://man7.org/linux/man-pages/man3/sysconf.3.html
*/
result<std::int64_t> conf(info r) noexcept {
LIBIPC_LOG();
switch (r) {
case info::page_size: {
auto val = ::sysconf(_SC_PAGESIZE);
if (val >= 0) return static_cast<std::int64_t>(val);
break;
}
default:
log.error("invalid info = ", underlyof(r));
return std::make_error_code(std::errc::invalid_argument);
}
auto err = sys::error();
log.error("info = ", underlyof(r), ", error = ", err);
return err;
}
} // namespace sys
} // namespace ipc

View File

@ -0,0 +1,95 @@
/**
* \file libipc/platform/win/codecvt.h
* \author mutouyun (orz@orzz.org)
*/
#pragma once
#include <Windows.h>
#include "libipc/imp/codecvt.h"
#include "libipc/imp/detect_plat.h"
namespace ipc {
/**
* \see 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-widechartomultibyte
*
* CP_ACP : The system default Windows ANSI code page.
* CP_MACCP : The current system Macintosh code page.
* CP_OEMCP : The current system OEM code page.
* CP_SYMBOL : Symbol code page (42).
* CP_THREAD_ACP: The Windows ANSI code page for the current thread.
* CP_UTF7 : UTF-7. Use this value only when forced by a 7-bit transport mechanism. Use of UTF-8 is preferred.
* CP_UTF8 : UTF-8.
*/
template <>
std::size_t cvt_cstr(char const *src, std::size_t slen, wchar_t *des, std::size_t dlen) noexcept {
if ((src == nullptr) || ((*src) == 0) || (slen == 0)) {
// source string is empty
return 0;
}
int cch_wc = (des == nullptr) ? 0 : (int)dlen;
int size_needed = ::MultiByteToWideChar(CP_ACP, 0, src, (int)slen, des, cch_wc);
if (size_needed <= 0) {
// failed: MultiByteToWideChar(CP_ACP).
return 0;
}
return size_needed;
}
template <>
std::size_t cvt_cstr(wchar_t const *src, std::size_t slen, char *des, std::size_t dlen) noexcept {
if ((src == nullptr) || ((*src) == 0) || (slen == 0)) {
// source string is empty
return 0;
}
int cb_mb = (des == nullptr) ? 0 : (int)dlen;
int size_needed = ::WideCharToMultiByte(CP_ACP, 0, src, (int)slen, des, cb_mb, NULL, NULL);
if (size_needed <= 0) {
// failed: WideCharToMultiByte(CP_ACP).
return 0;
}
return size_needed;
}
/**
* \brief Used for char8_t (since C++20) to wchar_t conversion.
*
* There is no ut to guarantee correctness (I'm a little lazy here),
* so if there are any bugs, please contact me in time.
*/
#if defined(LIBIMP_CPP_20)
template <>
std::size_t cvt_cstr(char8_t const *src, std::size_t slen, wchar_t *des, std::size_t dlen) noexcept {
if ((src == nullptr) || ((*src) == 0) || (slen == 0)) {
// source string is empty
return 0;
}
int cch_wc = (des == nullptr) ? 0 : (int)dlen;
int size_needed = ::MultiByteToWideChar(CP_UTF8, 0, (char *)src, (int)slen, des, cch_wc);
if (size_needed <= 0) {
// failed: MultiByteToWideChar(CP_UTF8).
return 0;
}
return size_needed;
}
template <>
std::size_t cvt_cstr(wchar_t const *src, std::size_t slen, char8_t *des, std::size_t dlen) noexcept {
if ((src == nullptr) || ((*src) == 0) || (slen == 0)) {
// source string is empty
return 0;
}
int cb_mb = (des == nullptr) ? 0 : (int)dlen;
int size_needed = ::WideCharToMultiByte(CP_UTF8, 0, src, (int)slen, (char *)des, cb_mb, NULL, NULL);
if (size_needed <= 0) {
// failed: WideCharToMultiByte(CP_UTF8).
return 0;
}
return size_needed;
}
#endif // defined(LIBIMP_CPP_20)
} // namespace ipc

View File

@ -4,7 +4,11 @@
#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"
@ -81,7 +85,7 @@ public:
if (!valid()) return false;
auto &cnt = counter();
{
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> guard {lock_};
LIBIPC_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);
@ -93,7 +97,7 @@ public:
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_};
LIBIPC_UNUSED std::lock_guard<ipc::sync::mutex> guard {lock_};
cnt -= 1;
}
return rs && rl;

View File

@ -0,0 +1,15 @@
/**
* \file libipc/platform/win/demangle.h
* \author mutouyun (orz@orzz.org)
*/
#pragma once
#include "libipc/imp/nameof.h"
namespace ipc {
std::string demangle(std::string name) noexcept {
return std::move(name);
}
} // namespace ipc

View File

@ -3,7 +3,11 @@
#include <cstdint>
#include <system_error>
#if defined(__MINGW32__)
#include <windows.h>
#else
#include <Windows.h>
#endif
#include "libipc/utility/log.h"
@ -51,7 +55,7 @@ public:
close();
}
static void clear_storage(char const */*name*/) noexcept {
static void clear_storage(char const * /*name*/) noexcept {
}
bool lock(std::uint64_t tm) noexcept {
@ -84,7 +88,7 @@ public:
return false;
case WAIT_ABANDONED:
unlock();
IPC_FALLTHROUGH_;
LIBIPC_FALLTHROUGH;
default:
ipc::error("fail WaitForSingleObject[%lu]: 0x%08X\n", ::GetLastError(), ret);
throw std::system_error{static_cast<int>(ret), std::system_category()};

View File

@ -2,7 +2,11 @@
#include <cstdint>
#if defined(__MINGW32__)
#include <windows.h>
#else
#include <Windows.h>
#endif
#include "libipc/utility/log.h"
@ -50,7 +54,7 @@ public:
close();
}
static void clear_storage(char const */*name*/) noexcept {
static void clear_storage(char const * /*name*/) noexcept {
}
bool wait(std::uint64_t tm) noexcept {

View File

@ -1,27 +1,44 @@
#if defined(__MINGW32__)
#include <windows.h>
#else
#include <Windows.h>
#endif
#include <atomic>
#include <string>
#include <utility>
#include "libipc/shm.h"
#include "libipc/def.h"
#include "libipc/pool_alloc.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
#include "libipc/mem/new.h"
#include "to_tchar.h"
#include "get_sa.h"
namespace {
struct info_t {
std::atomic<std::int32_t> acc_;
};
struct id_info_t {
HANDLE h_ = NULL;
void* mem_ = nullptr;
std::size_t size_ = 0;
};
constexpr std::size_t calc_size(std::size_t size) {
return ((((size - 1) / alignof(info_t)) + 1) * alignof(info_t)) + sizeof(info_t);
}
inline auto& acc_of(void* mem, std::size_t size) {
return reinterpret_cast<info_t*>(static_cast<ipc::byte_t*>(mem) + size - sizeof(info_t))->acc_;
}
} // internal-linkage
namespace ipc {
@ -44,8 +61,9 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
}
// Creates or opens a named file mapping object for a specified file.
else {
std::size_t alloc_size = calc_size(size);
h = ::CreateFileMapping(INVALID_HANDLE_VALUE, detail::get_sa(), PAGE_READWRITE | SEC_COMMIT,
0, static_cast<DWORD>(size), fmt_name.c_str());
0, static_cast<DWORD>(alloc_size), fmt_name.c_str());
DWORD err = ::GetLastError();
// If the object exists before the function call, the function returns a handle to the existing object
// (with its current size, not the specified size), and GetLastError returns ERROR_ALREADY_EXISTS.
@ -58,18 +76,34 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
return nullptr;
}
}
auto ii = mem::alloc<id_info_t>();
auto ii = mem::$new<id_info_t>();
ii->h_ = h;
ii->size_ = size;
return ii;
}
std::int32_t get_ref(id_t) {
return 0;
std::int32_t get_ref(id_t id) {
if (id == nullptr) {
return 0;
}
auto ii = static_cast<id_info_t*>(id);
if (ii->mem_ == nullptr || ii->size_ == 0) {
return 0;
}
return acc_of(ii->mem_, calc_size(ii->size_)).load(std::memory_order_acquire);
}
void sub_ref(id_t) {
// Do Nothing.
void sub_ref(id_t id) {
if (id == nullptr) {
ipc::error("fail sub_ref: invalid id (null)\n");
return;
}
auto ii = static_cast<id_info_t*>(id);
if (ii->mem_ == nullptr || ii->size_ == 0) {
ipc::error("fail sub_ref: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
return;
}
acc_of(ii->mem_, calc_size(ii->size_)).fetch_sub(1, std::memory_order_acq_rel);
}
void * get_mem(id_t id, std::size_t * size) {
@ -96,9 +130,16 @@ void * get_mem(id_t id, std::size_t * size) {
ipc::error("fail VirtualQuery[%d]\n", static_cast<int>(::GetLastError()));
return nullptr;
}
ii->mem_ = mem;
ii->size_ = static_cast<std::size_t>(mem_info.RegionSize);
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);
}
@ -107,17 +148,21 @@ std::int32_t release(id_t id) noexcept {
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 ::UnmapViewOfFile(static_cast<LPCVOID>(ii->mem_));
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 0;
mem::$delete(ii);
return ret;
}
void remove(id_t id) noexcept {
@ -138,3 +183,4 @@ void remove(char const * name) noexcept {
} // namespace shm
} // namespace ipc

View File

@ -0,0 +1,88 @@
/**
* \file libipc/platform/win/system.h
* \author mutouyun (orz@orzz.org)
*/
#pragma once
#include <exception>
#include <type_traits>
#include <Windows.h>
#include <tchar.h>
#include "libipc/imp/system.h"
#include "libipc/imp/log.h"
#include "libipc/imp/codecvt.h"
#include "libipc/imp/generic.h"
#include "libipc/imp/detect_plat.h"
#include "libipc/imp/scope_exit.h"
namespace ipc {
namespace sys {
/**
* \brief Gets a text description of the system error
* \see https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-formatmessage
*/
std::string error_string(DWORD code) noexcept {
LIBIPC_LOG();
LIBIPC_TRY {
LPTSTR lpErrText = NULL;
if (::FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
code,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpErrText,
0, NULL) == 0) {
log.error("failed: FormatMessage(dwMessageId = ", code, "). error = ", ::GetLastError());
return {};
}
LIBIPC_SCOPE_EXIT(finally) = [lpErrText] { ::LocalFree(lpErrText); };
std::size_t msg_len = ::_tcslen(lpErrText);
std::size_t len = cvt_cstr(lpErrText, msg_len, (char *)nullptr, 0);
if (len == 0) {
return {};
}
std::string ret(len, '\0');
cvt_cstr(lpErrText, msg_len, &ret[0], ret.size());
return ret;
} LIBIPC_CATCH(...) {
log.error("failed: FormatMessage(dwMessageId = ", code, ").",
"\n\texception: ", log::exception_string(std::current_exception()));
}
return {};
}
/**
* \brief Get the system error number.
* \see https://en.cppreference.com/w/cpp/error/system_category
* https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror
*/
std::error_code error() noexcept {
return std::error_code(::GetLastError(), std::system_category());
}
/**
* \brief Retrieves information about the current system.
* \see https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsysteminfo
* https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getnativesysteminfo
*/
result<std::int64_t> conf(info r) noexcept {
LIBIPC_LOG();
switch (r) {
case info::page_size: {
::SYSTEM_INFO info{};
::GetNativeSystemInfo(&info);
return (std::int64_t)info.dwPageSize;
}
default:
log.error("invalid info = ", underlyof(r));
return std::make_error_code(std::errc::invalid_argument);
}
}
} // namespace sys
} // namespace ipc

View File

@ -1,6 +1,10 @@
#pragma once
#if defined(__MINGW32__)
#include <windows.h>
#else
#include <Windows.h>
#endif
#include <type_traits>
#include <string>
@ -11,7 +15,7 @@
#include <cstddef>
#include "libipc/utility/concept.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
#include "libipc/platform/detail.h"
namespace ipc {
@ -36,7 +40,7 @@ using IsSameChar = ipc::require<is_same_char<T, S>::value, R>;
////////////////////////////////////////////////////////////////
template <typename T = TCHAR>
constexpr auto to_tchar(ipc::string &&str) -> IsSameChar<T, ipc::string, ipc::string &&> {
constexpr auto to_tchar(std::string &&str) -> IsSameChar<T, std::string, std::string &&> {
return std::move(str); // noconv
}
@ -48,7 +52,7 @@ constexpr auto to_tchar(ipc::string &&str) -> IsSameChar<T, ipc::string, ipc::st
* https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar
*/
template <typename T = TCHAR>
auto to_tchar(ipc::string &&external) -> IsSameChar<T, ipc::wstring> {
auto to_tchar(std::string &&external) -> IsSameChar<T, std::wstring> {
if (external.empty()) {
return {}; // noconv
}
@ -65,7 +69,7 @@ auto to_tchar(ipc::string &&external) -> IsSameChar<T, ipc::wstring> {
if (size_needed <= 0) {
return {};
}
ipc::wstring internal(size_needed, L'\0');
std::wstring internal(size_needed, L'\0');
::MultiByteToWideChar(CP_UTF8, 0, &external[0], (int)external.size(), &internal[0], size_needed);
return internal;
}

View File

@ -1,17 +0,0 @@
#include "libipc/pool_alloc.h"
#include "libipc/memory/resource.h"
namespace ipc {
namespace mem {
void* pool_alloc::alloc(std::size_t size) noexcept {
return async_pool_alloc::alloc(size);
}
void pool_alloc::free(void* p, std::size_t size) noexcept {
async_pool_alloc::free(p, size);
}
} // namespace mem
} // namespace ipc

View File

@ -18,7 +18,7 @@
#include "libipc/utility/log.h"
#include "libipc/platform/detail.h"
#include "libipc/circ/elem_def.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
namespace ipc {
namespace detail {
@ -63,8 +63,9 @@ public:
shm::handle::clear_storage(name);
}
bool connected() const noexcept {
return connected_ != 0;
template <typename Elems>
bool connected(Elems* elems) const noexcept {
return elems->connected(connected_);
}
circ::cc_t connected_id() const noexcept {
@ -77,16 +78,16 @@ public:
-> std::tuple<bool, bool, decltype(std::declval<Elems>().cursor())> {
if (elems == nullptr) return {};
// if it's already connected, just return
if (connected()) return {connected(), false, 0};
if (connected(elems)) return {connected(elems), false, 0};
connected_ = elems->connect_receiver();
return {connected(), true, elems->cursor()};
return {connected(elems), true, elems->cursor()};
}
template <typename Elems>
bool disconnect(Elems* elems) noexcept {
if (elems == nullptr) return false;
// if it's already disconnected, just return false
if (!connected()) return false;
if (!connected(elems)) return false;
elems->disconnect_receiver(std::exchange(connected_, 0));
return true;
}
@ -150,6 +151,10 @@ public:
elems_->disconnect_sender();
}
bool connected() const noexcept {
return base_t::connected(elems_);
}
bool connect() noexcept {
auto tp = base_t::connect(elems_);
if (std::get<0>(tp) && std::get<1>(tp)) {

View File

@ -6,7 +6,7 @@
#include "libipc/utility/pimpl.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
namespace ipc {
namespace shm {
@ -16,7 +16,7 @@ public:
shm::id_t id_ = nullptr;
void* m_ = nullptr;
ipc::string n_;
std::string n_;
std::size_t s_ = 0;
};

View File

@ -3,13 +3,13 @@
#include "libipc/utility/pimpl.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
#include "libipc/platform/detail.h"
#if defined(IPC_OS_WINDOWS_)
#if defined(LIBIPC_OS_WIN)
#include "libipc/platform/win/condition.h"
#elif defined(IPC_OS_LINUX_)
#elif defined(LIBIPC_OS_LINUX)
#include "libipc/platform/linux/condition.h"
#elif defined(IPC_OS_QNX_)
#elif defined(LIBIPC_OS_QNX)
#include "libipc/platform/posix/condition.h"
#else/*IPC_OS*/
# error "Unsupported platform."

View File

@ -3,13 +3,13 @@
#include "libipc/utility/pimpl.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
#include "libipc/platform/detail.h"
#if defined(IPC_OS_WINDOWS_)
#if defined(LIBIPC_OS_WIN)
#include "libipc/platform/win/mutex.h"
#elif defined(IPC_OS_LINUX_)
#elif defined(LIBIPC_OS_LINUX)
#include "libipc/platform/linux/mutex.h"
#elif defined(IPC_OS_QNX_)
#elif defined(LIBIPC_OS_QNX)
#include "libipc/platform/posix/mutex.h"
#else/*IPC_OS*/
# error "Unsupported platform."

View File

@ -3,11 +3,11 @@
#include "libipc/utility/pimpl.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
#include "libipc/platform/detail.h"
#if defined(IPC_OS_WINDOWS_)
#if defined(LIBIPC_OS_WIN)
#include "libipc/platform/win/semaphore.h"
#elif defined(IPC_OS_LINUX_) || defined(IPC_OS_QNX_)
#elif defined(LIBIPC_OS_LINUX) || defined(LIBIPC_OS_QNX)
#include "libipc/platform/posix/semaphore_impl.h"
#else/*IPC_OS*/
# error "Unsupported platform."

View File

@ -1,11 +1,11 @@
#include "libipc/waiter.h"
#include "libipc/platform/detail.h"
#if defined(IPC_OS_WINDOWS_)
#if defined(LIBIPC_OS_WIN)
#include "libipc/platform/win/mutex.h"
#elif defined(IPC_OS_LINUX_)
#elif defined(LIBIPC_OS_LINUX)
#include "libipc/platform/linux/mutex.h"
#elif defined(IPC_OS_QNX_)
#elif defined(LIBIPC_OS_QNX)
#include "libipc/platform/posix/mutex.h"
#else/*IPC_OS*/
# error "Unsupported platform."

View File

@ -5,7 +5,7 @@
#include "libipc/platform/detail.h"
#include "libipc/utility/concept.h"
#include "libipc/pool_alloc.h"
#include "libipc/mem/new.h"
namespace ipc {
@ -36,12 +36,12 @@ IPC_CONSTEXPR_ auto clear_impl(T* p) -> IsImplComfortable<T, void> {
template <typename T, typename... P>
IPC_CONSTEXPR_ auto make_impl(P&&... params) -> IsImplUncomfortable<T> {
return mem::alloc<T>(std::forward<P>(params)...);
return mem::$new<T>(std::forward<P>(params)...);
}
template <typename T>
IPC_CONSTEXPR_ auto clear_impl(T* p) -> IsImplUncomfortable<T, void> {
mem::free(p);
mem::$delete(p);
}
template <typename T>

View File

@ -27,7 +27,7 @@ constexpr decltype(auto) static_switch(std::size_t i, F&& f, D&& def) {
template <typename F, std::size_t...I>
IPC_CONSTEXPR_ void static_for(std::index_sequence<I...>, F&& f) {
IPC_UNUSED_ auto expand = { (std::forward<F>(f)(std::integral_constant<std::size_t, I>{}), 0)... };
LIBIPC_UNUSED auto expand = { (std::forward<F>(f)(std::integral_constant<std::size_t, I>{}), 0)... };
}
template <std::size_t N, typename F>
@ -44,18 +44,6 @@ enum {
// #endif/*__cplusplus < 201703L*/
};
template <typename T, typename U>
auto horrible_cast(U rhs) noexcept
-> typename std::enable_if<std::is_trivially_copyable<T>::value
&& std::is_trivially_copyable<U>::value, T>::type {
union {
T t;
U u;
} r = {};
r.u = rhs;
return r.t;
}
IPC_CONSTEXPR_ std::size_t make_align(std::size_t align, std::size_t size) {
// align must be 2^n
return (size + align - 1) & ~(align - 1);

View File

@ -63,7 +63,7 @@ public:
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_};
LIBIPC_UNUSED std::lock_guard<ipc::sync::mutex> guard {lock_};
while ([this, &pred] {
return !quit_.load(std::memory_order_relaxed)
&& std::forward<F>(pred)();
@ -75,14 +75,14 @@ public:
bool notify() noexcept {
{
IPC_UNUSED_ std::lock_guard<ipc::sync::mutex> barrier{lock_}; // barrier
LIBIPC_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
LIBIPC_UNUSED std::lock_guard<ipc::sync::mutex> barrier{lock_}; // barrier
}
return cond_.broadcast(lock_);
}

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

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

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

@ -15,7 +15,7 @@
#include "thread_pool.h"
#include "libipc/platform/detail.h"
#ifdef IPC_OS_LINUX_
#ifdef LIBIPC_OS_LINUX
#include <fcntl.h> // ::open
#endif
@ -88,7 +88,7 @@ inline static thread_pool & reader() {
return pool;
}
#ifdef IPC_OS_LINUX_
#ifdef LIBIPC_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) {
@ -100,7 +100,7 @@ inline bool check_exist(char const *name) noexcept {
#endif
inline bool expect_exist(char const *name, bool expected) noexcept {
#ifdef IPC_OS_LINUX_
#ifdef LIBIPC_OS_LINUX
return ipc_ut::check_exist(name) == expected;
#else
return true;

View File

@ -4,10 +4,11 @@
#include <mutex>
#include <atomic>
#include <cstring>
#include <new>
#include "libipc/ipc.h"
#include "libipc/buffer.h"
#include "libipc/memory/resource.h"
#include "libipc/mem/resource.h"
#include "test.h"
#include "thread_pool.h"
@ -84,18 +85,31 @@ void test_basic(char const * name) {
}
class data_set {
std::vector<rand_buf> datas_;
alignas(rand_buf) char datas_[sizeof(rand_buf[LoopCount])];
rand_buf *d_;
public:
data_set() {
datas_.resize(LoopCount);
// Use individual placement new instead of array placement new
// Array placement new adds a cookie (array size) before the array,
// which would overflow the buffer. MSVC's implementation stores this
// cookie, causing buffer overflow and subsequent memory corruption.
d_ = reinterpret_cast<rand_buf *>(datas_);
for (int i = 0; i < LoopCount; ++i) {
datas_[i].set_id(i);
::new(d_ + i) rand_buf;
d_[i].set_id(i);
}
}
std::vector<rand_buf> const &get() const noexcept {
return datas_;
~data_set() {
// Manually destroy each element since we used individual placement new
for (int i = 0; i < LoopCount; ++i) {
d_[i].~rand_buf();
}
}
rand_buf const *get() const noexcept {
return d_;
}
} const data_set__;
@ -112,7 +126,7 @@ void test_sr(char const * name, int s_cnt, int r_cnt) {
Que que { name, ipc::sender };
ASSERT_TRUE(que.wait_for_recv(r_cnt));
sw.start();
for (int i = 0; i < (int)data_set__.get().size(); ++i) {
for (int i = 0; i < LoopCount; ++i) {
ASSERT_TRUE(que.send(data_set__.get()[i]));
}
};
@ -128,7 +142,7 @@ void test_sr(char const * name, int s_cnt, int r_cnt) {
if (i == -1) {
return;
}
ASSERT_TRUE((i >= 0) && (i < (int)data_set__.get().size()));
ASSERT_TRUE((i >= 0) && (i < LoopCount));
auto const &data_set = data_set__.get()[i];
if (data_set != got) {
printf("data_set__.get()[%d] != got, size = %zd/%zd\n",
@ -146,7 +160,7 @@ void test_sr(char const * name, int s_cnt, int r_cnt) {
que.send(rand_buf{msg_head{-1}});
}
ipc_ut::reader().wait_for_done();
sw.print_elapsed<std::chrono::microseconds>(s_cnt, r_cnt, (int)data_set__.get().size(), name);
sw.print_elapsed<std::chrono::microseconds>(s_cnt, r_cnt, LoopCount, name);
}
} // internal-linkage

View File

@ -17,11 +17,11 @@ TEST(Platform, to_tchar) {
"\x81\xab\xe3\x81\xa1\xe3\x81\xaf";
wchar_t const *utf16 = L"hello world, \u4f60\u597d\uff0c\u3053\u3093\u306b\u3061\u306f";
{
ipc::string str = ipc::detail::to_tchar<char>(utf8);
std::string str = ipc::detail::to_tchar<char>(utf8);
EXPECT_STREQ(str.c_str(), utf8);
}
{
ipc::wstring wtr = ipc::detail::to_tchar<wchar_t>(utf8);
std::wstring wtr = ipc::detail::to_tchar<wchar_t>(utf8);
EXPECT_STREQ(wtr.c_str(), utf16);
//std::ofstream out("out.txt", std::ios::binary|std::ios::out);
//out.write((char const *)wtr.c_str(), wtr.size() * sizeof(wchar_t));

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

View File

@ -10,7 +10,7 @@
#include "test.h"
#include "libipc/platform/detail.h"
#if defined(IPC_OS_LINUX_)
#if defined(LIBIPC_OS_LINUX)
#include <pthread.h>
#include <time.h>
@ -36,8 +36,10 @@ TEST(PThread, Robust) {
pthread_mutex_unlock(&mutex);
pthread_mutex_destroy(&mutex);
}
#elif defined(IPC_OS_WINDOWS_)
#elif defined(LIBIPC_OS_WIN)
#include <Windows.h>
#include <Windows.h>
#endif
#include <tchar.h>
TEST(PThread, Robust) {

View File

@ -0,0 +1,103 @@
#include "../archive/test.h"
#define private public
#include "libipc/concur/intrusive_stack.h"
using namespace ipc;
TEST(intrusive_stack, construct) {
concur::intrusive_stack<int> s;
EXPECT_TRUE(s.empty());
}
TEST(intrusive_stack, construct_node) {
concur::intrusive_stack<int>::node n{};
EXPECT_TRUE(n.next.load(std::memory_order_relaxed) == nullptr);
}
TEST(intrusive_stack, copyable) {
EXPECT_FALSE(std::is_copy_constructible<concur::intrusive_stack<int>>::value);
EXPECT_FALSE(std::is_copy_assignable<concur::intrusive_stack<int>>::value);
}
TEST(intrusive_stack, moveable) {
EXPECT_FALSE(std::is_move_constructible<concur::intrusive_stack<int>>::value);
EXPECT_FALSE(std::is_move_assignable<concur::intrusive_stack<int>>::value);
}
TEST(intrusive_stack, push_one) {
concur::intrusive_stack<int> s;
concur::intrusive_stack<int>::node n{123};
s.push(&n);
EXPECT_FALSE(s.empty());
EXPECT_TRUE(s.top_.load(std::memory_order_relaxed) == &n);
EXPECT_TRUE(n.next.load(std::memory_order_relaxed) == nullptr);
EXPECT_EQ(n.value, 123);
}
TEST(intrusive_stack, push_many) {
concur::intrusive_stack<int> s;
concur::intrusive_stack<int>::node n1{111111};
concur::intrusive_stack<int>::node n2{222222};
concur::intrusive_stack<int>::node n3{333333};
s.push(&n1);
s.push(&n2);
s.push(&n3);
EXPECT_FALSE(s.empty());
EXPECT_TRUE(s.top_.load(std::memory_order_relaxed) == &n3);
EXPECT_TRUE(n3.next.load(std::memory_order_relaxed) == &n2);
EXPECT_TRUE(n2.next.load(std::memory_order_relaxed) == &n1);
EXPECT_TRUE(n1.next.load(std::memory_order_relaxed) == nullptr);
EXPECT_EQ(n1.value, 111111);
EXPECT_EQ(n2.value, 222222);
EXPECT_EQ(n3.value, 333333);
}
TEST(intrusive_stack, push_same) {
concur::intrusive_stack<int> s;
concur::intrusive_stack<int>::node n{321};
s.push(&n);
s.push(&n);
EXPECT_FALSE(s.empty());
EXPECT_TRUE(s.top_.load(std::memory_order_relaxed) == &n);
EXPECT_TRUE(n.next.load(std::memory_order_relaxed) == &n);
EXPECT_EQ(n.value, 321);
}
TEST(intrusive_stack, pop_empty) {
concur::intrusive_stack<int> s;
EXPECT_TRUE(s.pop() == nullptr);
}
TEST(intrusive_stack, pop_one) {
concur::intrusive_stack<int> s;
concur::intrusive_stack<int>::node n{112233};
s.push(&n);
EXPECT_TRUE(s.pop() == &n);
EXPECT_TRUE(s.empty());
EXPECT_TRUE(s.top_.load(std::memory_order_relaxed) == nullptr);
EXPECT_TRUE(n.next.load(std::memory_order_relaxed) == nullptr);
EXPECT_EQ(n.value, 112233);
}
TEST(intrusive_stack, pop_many) {
concur::intrusive_stack<int> s;
concur::intrusive_stack<int>::node n1{111111};
concur::intrusive_stack<int>::node n2{222222};
concur::intrusive_stack<int>::node n3{333333};
s.push(&n1);
s.push(&n2);
s.push(&n3);
EXPECT_TRUE(s.pop() == &n3);
EXPECT_TRUE(s.pop() == &n2);
EXPECT_TRUE(s.pop() == &n1);
EXPECT_TRUE(s.empty());
EXPECT_TRUE(s.top_.load(std::memory_order_relaxed) == nullptr);
EXPECT_TRUE(n3.next.load(std::memory_order_relaxed) == &n2);
EXPECT_TRUE(n2.next.load(std::memory_order_relaxed) == &n1);
EXPECT_TRUE(n1.next.load(std::memory_order_relaxed) == nullptr);
EXPECT_EQ(n1.value, 111111);
EXPECT_EQ(n2.value, 222222);
EXPECT_EQ(n3.value, 333333);
}

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