Compare commits

...

63 Commits

Author SHA1 Message Date
木头云
858a7a6e38
Merge pull request #167 from mutouyun/refactor/log
refactor(log): Remove deprecated utility/log.h
2025-12-18 11:38:22 +08:00
木头云
d6e8f71780 refactor(log): remove deprecated utility/log.h
- Remove src/libipc/utility/log.h as it's no longer used
- All code has been migrated to use the new libipc/imp/log.h interface
- The old utility/log.h provided printf-style logging: ipc::log() and ipc::error()
- The new imp/log.h provides modern C++ stream-based logging with LIBIPC_LOG() macro
- Verified that there are no remaining references to utility/log.h in the codebase

This completes the log interface migration by removing the deprecated file.
2025-12-18 03:34:24 +00:00
木头云
3269bde1a5
Merge pull request #166 from mutouyun/feature/imp-log
refactor(log): Replace utility/log with imp/log interface
2025-12-15 20:18:06 +08:00
木头云
ab8e6c7d2c fix(log): move sa_initiator struct outside get_sa() function
- src/libipc/platform/win/get_sa.h:
  * Move 'struct initiator' definition outside get_sa() function
  * Rename to 'sa_initiator' to avoid naming conflicts
  * Define at namespace ipc::detail scope (above get_sa function)
  * Keep template constructor: template <typename Logger> sa_initiator(Logger const &log)
  * get_sa() now simply uses: static sa_initiator handle(log);

This fixes the C++ standard violation:
  - C++03/11/14/17/20 all prohibit local classes from having member templates
  - Error C2892: 'local class shall not have member templates'
  - Moving the struct to namespace scope resolves this issue

The struct is now a proper namespace-level definition with a template
constructor, which is fully compliant with C++ standards.
2025-12-15 12:10:26 +00:00
木头云
72eedeb6c4 fix(log): use constructor template instead of class template for initiator
- src/libipc/platform/win/get_sa.h:
  * Change from class template to constructor template
  * Keep 'struct initiator' as a regular class (not template)
  * Make constructor a function template: template <typename Logger> initiator(Logger const &log)
  * Instantiate as: static initiator handle(log);
  * This is valid C++ as function templates can be defined inside functions
  * Fixes the issue that class templates cannot be defined inside functions

The constructor template approach allows proper logger passing while
maintaining valid C++ syntax for local struct definitions.
2025-12-15 11:58:13 +00:00
木头云
afd1467e03 fix(log): use template to pass logger to initiator and fix format error
- src/libipc/platform/win/get_sa.h:
  * Convert initiator struct to template with Logger parameter
  * Pass log object from get_sa() to initiator constructor via template
  * Use 'static initiator<decltype(log)> handle(log)' to instantiate
  * This allows initiator constructor to properly access log object
  * Syntax: initiator(Logger const &log) receives the logger

- src/libipc/platform/win/semaphore.h:
  * Fix format error on line 79: remove extra characters '"}]'
  * Correct closing of log.error() statement
  * Before: log.error(...)"}]
  * After:  log.error(...);

These fixes resolve the static struct initialization issue and
code format error in Windows platform.
2025-12-15 11:54:04 +00:00
木头云
66a66f15ec fix(log): fix Windows platform compilation errors
- src/libipc/platform/win/get_sa.h:
  * Fix malformed log.error() calls on lines 19 and 23
  * Remove extra comma and parenthesis: GetLastError(, -> GetLastError()
  * Fix closing parenthesis and bracket placement
  * Line 19: GetLastError(, "]"))) -> GetLastError()), "]"
  * Line 23: GetLastError(, "]"))) -> GetLastError()), "]"

- src/libipc/platform/win/mutex.h:
  * Add missing LIBIPC_LOG() to try_lock() function at line 84
  * The function uses log.error() at line 95 and needs logger initialization

These fixes resolve Windows compilation errors related to malformed
log calls and missing LIBIPC_LOG() macro.
2025-12-15 11:42:57 +00:00
木头云
0f8bd3415c fix(log): add missing LIBIPC_LOG() in get_info member function
- src/libipc/ipc.cpp:
  * Add LIBIPC_LOG() to chunk_storages::get_info() member function
  * This was missing, causing 'log' to be undeclared at line 245
  * The get_info() function uses log.error() for chunk storage errors

This completes the fix for all missing LIBIPC_LOG() initializations
in the ipc.cpp file.
2025-12-15 11:33:17 +00:00
木头云
73d59ba20e fix(log): add missing LIBIPC_LOG() and fix lambda log capture
- src/libipc/prod_cons.h:
  * Add LIBIPC_LOG() to second force_push() template function
  * This was missing, causing 'log' to be undeclared at line 379

- src/libipc/ipc.cpp:
  * Add LIBIPC_LOG() to static send() function (line 590)
  * Capture log by reference in outer lambda: [tm, &log]
  * Capture log by reference in inner lambda: [tm, &log, info, que, msg_id]
  * This fixes 'log' was not declared error in lambda at line 598
  * The log variable is now properly captured from the outer send() scope

These fixes ensure that all functions using log.debug/error/warning
have proper LIBIPC_LOG() initialization and lambda captures.
2025-12-15 10:21:40 +00:00
木头云
2b1ed4bc51 fix(log): remove remaining format specifiers and fix malformed log calls
- src/libipc/platform/posix/condition.h:
  * Replace all %d and %s format specifiers with stream-based syntax
  * Update log.error() calls to use proper streaming (e.g., "[", eno, "]")

- src/libipc/platform/posix/semaphore_impl.h:
  * Remove %d format specifiers from log.error() calls
  * Fix malformed parentheses (e.g., .c_str(, ""))
  * Remove unnecessary empty string arguments
  * Use stream-based logging consistently

- src/libipc/platform/win/mutex.h:
  * Fix malformed GetLastError() parentheses
  * Remove %lu format specifier, use explicit cast instead
  * Update to stream-based logging syntax

- src/libipc/platform/win/semaphore.h:
  * Fix malformed GetLastError() parentheses
  * Remove %lu format specifier, use explicit cast instead
  * Update to stream-based logging syntax

All format specifiers (%d, %s, %zd, %p, %lu) have been removed and replaced
with proper C++ stream-based logging that is type-safe and consistent with
the new imp/log interface.
2025-12-15 10:06:52 +00:00
木头云
1664526c40 fix(log): fix malformed log calls and add missing LIBIPC_LOG() in shm files
- src/libipc/platform/posix/shm_posix.cpp:
  * Add LIBIPC_LOG() to acquire() and get_mem() functions
  * Fix malformed log.error() calls: remove format specifiers (%d, %zd, %p)
  * Fix parentheses errors in log.error() calls (e.g., .c_str(, ""))
  * Use stream-based logging instead of printf-style formatting

- src/libipc/platform/win/shm_win.cpp:
  * Add LIBIPC_LOG() to acquire() and get_mem() functions
  * Fix malformed log.error() calls with GetLastError()
  * Fix parentheses errors in log.error() calls
  * Ensure consistent stream-based logging syntax

These fixes address syntax errors that would have caused compilation failures.
2025-12-15 10:02:23 +00:00
木头云
2ff5c94479 fix(log): add missing LIBIPC_LOG() to all functions using log interface
- Add LIBIPC_LOG() to functions in platform files that use log.error/warning/debug
- Fixed files:
  - POSIX platform: mutex.h, semaphore_impl.h, shm_posix.cpp
  - Windows platform: get_sa.h, mutex.h, semaphore.h, shm_win.cpp
  - Sync layer: condition.cpp, mutex.cpp, semaphore.cpp

All functions using the new log interface now properly initialize the logger with LIBIPC_LOG()
2025-12-15 09:48:54 +00:00
木头云
298354973a fix(log): add missing LIBIPC_LOG() in posix get_wait_time.h
- Add LIBIPC_LOG() to calc_wait_time() function
- Add LIBIPC_LOG() to make_timespec() function
- Both functions use log.error() and need the logger initialization
2025-12-15 09:47:39 +00:00
木头云
0c4421d5c2 refactor(log): fix remaining complex log format calls
- Fix multi-parameter log calls with complex formatting in POSIX and Windows platforms
- Replace remaining ipc::error() and ipc::log() calls with log.error() and log.warning()
- Handle special cases:
  - POSIX condition.h: pthread_cond_timedwait multi-param formatting
  - POSIX get_wait_time.h: calc_wait_time multi-param formatting
  - POSIX semaphore_impl.h: sem_timedwait multi-param formatting
  - Windows mutex.h: WaitForSingleObject with hex formatting, WAIT_ABANDONED as warning
  - Windows semaphore.h: WaitForSingleObject and ReleaseSemaphore calls
- Use std::hex/std::dec for hexadecimal formatting in Windows platform
- All log interface migrations now complete
2025-12-15 09:40:37 +00:00
木头云
e9a7dbaa74 refactor(log): replace utility/log with imp/log in all platform and sync files
- Replace include "libipc/utility/log.h" with "libipc/imp/log.h" in all files
- Add LIBIPC_LOG() to functions that use logging
- Replace ipc::error() and ipc::log() calls with log.error() and log.debug()
- Use type-safe streaming interface instead of printf-style formatting
- Remove manual newline characters from log messages

Modified files:
- Linux platform: condition.h, get_wait_time.h, mutex.h, sync_obj_impl.h
- POSIX platform: condition.h, get_wait_time.h, mutex.h, semaphore_impl.h, shm_posix.cpp
- Windows platform: condition.h, get_sa.h, mutex.h, semaphore.h, shm_win.cpp
- Sync layer: condition.cpp, mutex.cpp, semaphore.cpp

Total: 17 files updated with comprehensive log interface migration
2025-12-15 09:37:50 +00:00
木头云
6143c23fd3 refactor(log): replace utility/log with imp/log in ipc.cpp
- Replace include "libipc/utility/log.h" with "libipc/imp/log.h"
- Add LIBIPC_LOG() at the beginning of functions that use logging
- Replace all ipc::error() calls with log.error()
- Replace all ipc::log() calls with log.debug() or log.error() based on context
- Modified functions:
  - cc_acc(): error logging for shm acquire failure
  - make_handle(): error logging for chunk storage operations
  - find_storage(): error logging for invalid storage id
  - release_storage(): error logging for invalid storage id
  - recycle_storage(): error logging for invalid storage id
  - clear_message(): error logging for invalid message size
  - send(): error logging for various send failures, debug logging for force_push
  - recv(): error logging for various recv failures
- Use type-safe streaming interface instead of printf-style formatting
- Remove manual newline characters from log messages
- Total changes: 19 log call sites updated
2025-12-15 09:02:00 +00:00
木头云
309baf77bc refactor(log): change log level from warning to debug in prod_cons.h
- Update force_push() log calls to use log.debug() instead of log.warning()
- Debug level is more appropriate for internal force_push diagnostic messages
2025-12-15 08:47:42 +00:00
木头云
bf62216e8d refactor(log): replace utility/log with imp/log in prod_cons.h and queue.h
- Replace include "libipc/utility/log.h" with "libipc/imp/log.h"
- prod_cons.h: Replace ipc::log() calls with LIBIPC_LOG() + log.warning()
  - Updated 2 force_push() template functions in broadcast implementations
  - Changed log level from generic log to warning for force_push scenarios
- queue.h: Replace ipc::error() calls with LIBIPC_LOG() + log.error()
  - Updated queue_conn::open() template function
- Use type-safe streaming interface instead of printf-style formatting
- Remove manual newline characters from log messages
2025-12-15 08:43:13 +00:00
木头云
2ec1914691 refactor(log): replace utility/log with imp/log in shm.cpp
- Replace include "libipc/utility/log.h" with "libipc/imp/log.h"
- Replace ipc::error() calls with LIBIPC_LOG() + log.error()
- Use type-safe streaming interface instead of printf-style formatting
- Remove manual newline characters from log messages
2025-12-15 08:33:43 +00:00
木头云
78bbb05322 feat(ci): exclude 3rdparty folder from codecov coverage
- Add codecov.yml configuration to exclude 3rdparty, test, and demo directories
- Update GitHub Actions workflow with exclude parameter in codecov-action
- Ensures only project source code is included in coverage metrics

This provides double protection:
1. codecov.yml filters on CodeCov service side
2. workflow exclude filters during upload
2025-12-12 07:04:31 +00:00
木头云
dc809e0da8 feat(ci): add CodeCov workflow for test coverage reporting 2025-12-12 07:04:31 +00:00
mutouyun
3ca2e93b19 feat(ci): add codecov test coverage support to master branch
Add CodeCov test coverage configuration:
- Add LIBIPC_CODECOV option to CMakeLists.txt with coverage compilation flags
- Add CodeCov badge to README.md for coverage status display

Note: The codecov.yml workflow file needs to be added manually
or requires workflows permission to push.
2025-12-12 07:04:31 +00:00
mutouyun
e38d3ed801 fix(platform): Add FreeBSD detection and include detect_plat.h in detail.h
Fixed two critical issues from the rebase:
1. Added LIBIPC_OS_FREEBSD macro definition in detect_plat.h to enable
   FreeBSD platform detection alongside other OS checks
2. Added missing #include "libipc/imp/detect_plat.h" in detail.h to
   properly include platform detection macros

These fixes ensure FreeBSD compilation will work correctly with the
unified platform detection system.
2025-12-12 07:04:31 +00:00
木头云
542706117a 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-12 07:04:31 +00:00
木头云
fc7fa158b6 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-12 07:04:31 +00:00
木头云
cc33f73eec 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-12 07:04:31 +00:00
木头云
489e75870d 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-12 07:04:31 +00:00
木头云
8731413d30 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-12 07:04:31 +00:00
木头云
c39a0cba49 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-12 07:04:31 +00:00
mutouyun
90b7badcee Replace custom hash struct with std::hash in unordered_map definition 2025-12-12 07:04:31 +00:00
mutouyun
165060e0b6 Fix the issue caused by inconsistent lifecycle of the global IPC object. 2025-12-12 07:04:31 +00:00
mutouyun
63f35484d7 Refactoring the generic memory allocator 2025-12-12 07:04:31 +00:00
mutouyun
716992cade Reimplement the allocator required for the container type with $new 2025-12-12 07:04:31 +00:00
mutouyun
41529e8eb3 Use $new instead of alloc 2025-12-12 07:04:31 +00:00
mutouyun
189d5f348c Simplify the implementation of memory allocation management 2025-12-12 07:04:31 +00:00
mutouyun
39c7c5c70b The memory allocator supports runtime dynamic size memory allocation 2025-12-12 07:04:31 +00:00
mutouyun
00ee30e339 libipc/memory/resource.h => libipc/mem/resource.h 2025-12-12 07:04:31 +00:00
mutouyun
22c805c7cb Add $new 2025-12-12 07:04:31 +00:00
mutouyun
9a2e1b237a Add block_pool 2025-12-12 07:04:31 +00:00
mutouyun
0035764e0d Adjust the allocator name 2025-12-12 07:04:31 +00:00
mutouyun
93d6c44771 Simplify verify_args function to fix error C3249 2025-12-12 07:04:31 +00:00
mutouyun
9276fcbcda Fix fmt function to handle null pointers and return empty string 2025-12-12 07:04:31 +00:00
木头云
077288e911 Update c-cpp.yml 2025-12-12 07:04:31 +00:00
mutouyun
d00dcbbf15 Fix fmt function to handle empty strings and update make_prefix template parameters 2025-12-12 07:04:31 +00:00
mutouyun
c77a29e9fb Optimize memory_resource & add monotonic_buffer_resource 2025-12-12 07:04:31 +00:00
mutouyun
3693a85aca Add intrusive_stack 2025-12-12 07:04:31 +00:00
mutouyun
397b362338 Add allocator and rewrite allocator_wrapper 2025-12-12 07:04:31 +00:00
mutouyun
7536757064 Optimized partial implementation using fmt 2025-12-12 07:04:31 +00:00
mutouyun
106837ffce Start refactoring memory management, adding memory_resource 2025-12-12 07:04:31 +00:00
mutouyun
975ecba2e3 Update platform-specific feature macros to new interfaces in imp 2025-12-12 07:04:31 +00:00
mutouyun
52951e44f8 Add system 2025-12-12 07:04:31 +00:00
mutouyun
5f75f1d738 Add result 2025-12-12 07:04:31 +00:00
mutouyun
8ec7c6a486 IPC_EXPORT => LIBIPC_EXPORT 2025-12-12 07:04:31 +00:00
mutouyun
ecd1aaac40 Add log 2025-12-12 07:04:31 +00:00
mutouyun
208ac889e7 Add error 2025-12-12 07:04:31 +00:00
mutouyun
d20f88c349 Added fmt support for byte 2025-12-12 07:04:31 +00:00
mutouyun
bc29c85c1a Add fmt 2025-12-12 07:04:31 +00:00
mutouyun
95bf3afe9b Add codecvt 2025-12-12 07:04:31 +00:00
mutouyun
f3ca9a30e8 libimp => libipc 2025-12-12 07:04:31 +00:00
mutouyun
a4eeac7217 Add nameof & scope_exit 2025-12-12 07:04:31 +00:00
mutouyun
1eaadf80f2 Move the export.h file to the imp directory 2025-12-12 07:04:31 +00:00
mutouyun
bf7c6b41e9 Add expected 2025-12-12 07:04:31 +00:00
mutouyun
2e7ab8b34f Add imp for subsequent refactoring 2025-12-12 07:04:31 +00:00
117 changed files with 6761 additions and 1793 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 ]

38
.github/workflows/codecov.yml vendored Normal file
View File

@ -0,0 +1,38 @@
name: Upload CodeCov Report
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Configure
run: cmake -DCMAKE_BUILD_TYPE=Debug -DLIBIPC_BUILD_TESTS=ON -DLIBIPC_CODECOV=ON .
- name: Build
run: make -j
- name: Test
env:
LD_LIBRARY_PATH: ./bin
run: ./bin/test-ipc
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4.0.1
with:
verbose: true
exclude: |
3rdparty/**
test/**
demo/**
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@ -5,6 +5,7 @@ option(LIBIPC_BUILD_TESTS "Build all of libipc's own tests."
option(LIBIPC_BUILD_DEMOS "Build all of libipc's own demos." OFF)
option(LIBIPC_BUILD_SHARED_LIBS "Build shared libraries (DLLs)." OFF)
option(LIBIPC_USE_STATIC_CRT "Set to ON to build with static CRT on Windows (/MT)." OFF)
option(LIBIPC_CODECOV "Build with unit test coverage." OFF)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_CXX_STANDARD 17)
@ -13,6 +14,12 @@ if(NOT MSVC)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
endif()
# Code coverage support
if (LIBIPC_CODECOV AND NOT MSVC)
add_compile_options(--coverage)
add_link_options(-lgcov --coverage)
endif()
if (MSVC)
set(CompilerFlags
CMAKE_CXX_FLAGS

View File

@ -2,6 +2,7 @@
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/mutouyun/cpp-ipc/blob/master/LICENSE)
[![Build Status](https://github.com/mutouyun/cpp-ipc/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/mutouyun/cpp-ipc/actions)
[![CodeCov](https://codecov.io/github/mutouyun/cpp-ipc/graph/badge.svg?token=MNOAOLNELH)](https://codecov.io/github/mutouyun/cpp-ipc)
[![Build status](https://ci.appveyor.com/api/projects/status/github/mutouyun/cpp-ipc?branch=master&svg=true)](https://ci.appveyor.com/project/mutouyun/cpp-ipc)
[![Vcpkg package](https://img.shields.io/badge/Vcpkg-package-blueviolet)](https://github.com/microsoft/vcpkg/tree/master/ports/cpp-ipc)

30
codecov.yml Normal file
View File

@ -0,0 +1,30 @@
codecov:
require_ci_to_pass: yes
coverage:
precision: 2
round: down
range: "70...100"
status:
project:
default:
target: auto
threshold: 0%
base: auto
patch:
default:
target: auto
threshold: 0%
base: auto
ignore:
- "3rdparty/**/*"
- "3rdparty/**"
- "test/**/*"
- "demo/**/*"
comment:
layout: "reach,diff,flags,tree"
behavior: default
require_changes: false

View File

@ -5,12 +5,12 @@
#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);
@ -59,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,228 @@
/**
* \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(__FreeBSD__)
# define LIBIPC_OS_FREEBSD
#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

@ -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,8 +15,8 @@ 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);
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);
// 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:
@ -25,7 +25,7 @@ IPC_EXPORT void * get_mem(id_t id, std::size_t * size);
// 3. Frees the id structure
// After calling this function, the id becomes invalid and must not be used again.
// Returns: The reference count before decrement, or -1 on error.
IPC_EXPORT std::int32_t release(id_t id) noexcept;
LIBIPC_EXPORT std::int32_t release(id_t id) noexcept;
// Release shared memory resource and force cleanup of disk file.
// This function calls release(id) internally, then unconditionally attempts to
@ -34,19 +34,19 @@ IPC_EXPORT std::int32_t release(id_t id) noexcept;
// not in combination with release().
// Typical use case: Force cleanup when you want to ensure the disk file is removed
// regardless of reference count state.
IPC_EXPORT void remove (id_t id) noexcept;
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.
IPC_EXPORT void remove (char const * name) noexcept;
LIBIPC_EXPORT void remove (char const * name) noexcept;
IPC_EXPORT std::int32_t get_ref(id_t id);
IPC_EXPORT void sub_ref(id_t id);
LIBIPC_EXPORT std::int32_t get_ref(id_t id);
LIBIPC_EXPORT void sub_ref(id_t id);
class IPC_EXPORT handle {
class LIBIPC_EXPORT handle {
public:
handle();
handle(char const * name, std::size_t size, unsigned mode = create | open);
@ -83,3 +83,4 @@ private:
} // namespace shm
} // namespace ipc

View File

@ -5,6 +5,8 @@ set (PACKAGE_VERSION 1.4.1)
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

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

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,18 +14,18 @@
#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"
#include "libipc/waiter.h"
#include "libipc/utility/log.h"
#include "libipc/imp/log.h"
#include "libipc/utility/id_pool.h"
#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,31 @@ 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) {
LIBIPC_LOG();
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());
log.error("[cc_acc] acquire failed: ", shm_name);
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 +111,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 +123,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 +152,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,14 +214,15 @@ 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) {
LIBIPC_LOG();
if (!h.valid() &&
!h.acquire( shm_name.c_str(),
sizeof(chunk_info_t) + chunk_info_t::chunks_mem_size(chunk_size) )) {
ipc::error("[chunk_storages] chunk_shm.id_info_.acquire failed: chunk_size = %zd\n", chunk_size);
log.error("[chunk_storages] chunk_shm.id_info_.acquire failed: chunk_size = ", chunk_size);
return false;
}
return true;
@ -223,8 +230,9 @@ 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)})};
LIBIPC_LOG();
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_};
@ -235,7 +243,7 @@ auto& chunk_storages() {
}
auto *info = static_cast<chunk_info_t*>(h->get());
if (info == nullptr) {
ipc::error("[chunk_storages] chunk_shm.id_info_.get failed: chunk_size = %zd\n", chunk_size);
log.error("[chunk_storages] chunk_shm.id_info_.get failed: chunk_size = ", chunk_size);
return nullptr;
}
return info;
@ -243,8 +251,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 +260,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;
}
}
@ -285,8 +293,9 @@ std::pair<ipc::storage_id_t, void*> acquire_storage(conn_info_head *inf, std::si
}
void *find_storage(ipc::storage_id_t id, conn_info_head *inf, std::size_t size) {
LIBIPC_LOG();
if (id < 0) {
ipc::error("[find_storage] id is invalid: id = %ld, size = %zd\n", (long)id, size);
log.error("[find_storage] id is invalid: id = ", (long)id, ", size = ", size);
return nullptr;
}
std::size_t chunk_size = calc_chunk_size(size);
@ -296,8 +305,9 @@ void *find_storage(ipc::storage_id_t id, conn_info_head *inf, std::size_t size)
}
void release_storage(ipc::storage_id_t id, conn_info_head *inf, std::size_t size) {
LIBIPC_LOG();
if (id < 0) {
ipc::error("[release_storage] id is invalid: id = %ld, size = %zd\n", (long)id, size);
log.error("[release_storage] id is invalid: id = ", (long)id, ", size = ", size);
return;
}
std::size_t chunk_size = calc_chunk_size(size);
@ -329,8 +339,9 @@ bool sub_rc(ipc::wr<Rp, Rc, ipc::trans::broadcast>,
template <typename Flag>
void recycle_storage(ipc::storage_id_t id, conn_info_head *inf, std::size_t size, ipc::circ::cc_t curr_conns, ipc::circ::cc_t conn_id) {
LIBIPC_LOG();
if (id < 0) {
ipc::error("[recycle_storage] id is invalid: id = %ld, size = %zd\n", (long)id, size);
log.error("[recycle_storage] id is invalid: id = ", (long)id, ", size = ", size);
return;
}
std::size_t chunk_size = calc_chunk_size(size);
@ -350,11 +361,12 @@ void recycle_storage(ipc::storage_id_t id, conn_info_head *inf, std::size_t size
template <typename MsgT>
bool clear_message(conn_info_head *inf, void* p) {
LIBIPC_LOG();
auto msg = static_cast<MsgT*>(p);
if (msg->storage_) {
std::int32_t r_size = static_cast<std::int32_t>(ipc::data_length) + msg->remain_;
if (r_size <= 0) {
ipc::error("[clear_message] invalid msg size: %d\n", (int)r_size);
log.error("[clear_message] invalid msg size: ", (int)r_size);
return true;
}
release_storage(*reinterpret_cast<ipc::storage_id_t*>(&msg->data_),
@ -394,11 +406,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 +420,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 +459,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 +502,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 {
@ -513,33 +525,34 @@ static bool wait_for_recv(ipc::handle_t h, std::size_t r_count, std::uint64_t tm
template <typename F>
static bool send(F&& gen_push, ipc::handle_t h, void const * data, std::size_t size) {
LIBIPC_LOG();
if (data == nullptr || size == 0) {
ipc::error("fail: send(%p, %zd)\n", data, size);
log.error("fail: send(", data, ", ", size, ")");
return false;
}
auto que = queue_of(h);
if (que == nullptr) {
ipc::error("fail: send, queue_of(h) == nullptr\n");
log.error("fail: send, queue_of(h) == nullptr");
return false;
}
if (que->elems() == nullptr) {
ipc::error("fail: send, queue_of(h)->elems() == nullptr\n");
log.error("fail: send, queue_of(h)->elems() == nullptr");
return false;
}
if (!que->ready_sending()) {
ipc::error("fail: send, que->ready_sending() == false\n");
log.error("fail: send, que->ready_sending() == false");
return false;
}
ipc::circ::cc_t conns = que->elems()->connections(std::memory_order_relaxed);
if (conns == 0) {
ipc::error("fail: send, there is no receiver on this connection.\n");
log.error("fail: send, there is no receiver on this connection.");
return false;
}
// calc a new message id
conn_info_t *inf = info_of(h);
auto acc = inf->acc();
if (acc == nullptr) {
ipc::error("fail: send, info_of(h)->acc() == nullptr\n");
log.error("fail: send, info_of(h)->acc() == nullptr");
return false;
}
auto msg_id = acc->fetch_add(1, std::memory_order_relaxed);
@ -553,7 +566,7 @@ static bool send(F&& gen_push, ipc::handle_t h, void const * data, std::size_t s
static_cast<std::int32_t>(ipc::data_length), &(dat.first), 0);
}
// try using message fragment
//ipc::log("fail: shm::handle for big message. msg_id: %zd, size: %zd\n", msg_id, size);
//log.debug("fail: shm::handle for big message. msg_id: ", msg_id, ", size: ", size);
}
// push message fragment
std::int32_t offset = 0;
@ -576,14 +589,15 @@ static bool send(F&& gen_push, ipc::handle_t h, void const * data, std::size_t s
}
static bool send(ipc::handle_t h, void const * data, std::size_t size, std::uint64_t tm) {
return send([tm](auto *info, auto *que, auto msg_id) {
return [tm, info, que, msg_id](std::int32_t remain, void const * data, std::size_t size) {
LIBIPC_LOG();
return send([tm, &log](auto *info, auto *que, auto msg_id) {
return [tm, &log, info, que, msg_id](std::int32_t remain, void const * data, std::size_t size) {
if (!wait_for(info->wt_waiter_, [&] {
return !que->push(
[](void*) { return true; },
info->cc_id_, msg_id, remain, data, size);
}, tm)) {
ipc::log("force_push: msg_id = %zd, remain = %d, size = %zd\n", msg_id, remain, size);
log.debug("force_push: msg_id = ", msg_id, ", remain = ", remain, ", size = ", size);
if (!que->force_push(
[info](void* p) { return clear_message<typename queue_t::value_t>(info, p); },
info->cc_id_, msg_id, remain, data, size)) {
@ -613,9 +627,10 @@ static bool try_send(ipc::handle_t h, void const * data, std::size_t size, std::
}
static ipc::buff_t recv(ipc::handle_t h, std::uint64_t tm) {
LIBIPC_LOG();
auto que = queue_of(h);
if (que == nullptr) {
ipc::error("fail: recv, queue_of(h) == nullptr\n");
log.error("fail: recv, queue_of(h) == nullptr");
return {};
}
if (!que->connected()) {
@ -643,7 +658,7 @@ static ipc::buff_t recv(ipc::handle_t h, std::uint64_t tm) {
// msg.remain_ may minus & abs(msg.remain_) < data_length
std::int32_t r_size = static_cast<std::int32_t>(ipc::data_length) + msg.remain_;
if (r_size <= 0) {
ipc::error("fail: recv, r_size = %d\n", (int)r_size);
log.error("fail: recv, r_size = ", (int)r_size);
return {};
}
std::size_t msg_size = static_cast<std::size_t>(r_size);
@ -657,20 +672,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");
log.error("fail: ipc::mem::$new<recycle_t>.");
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,
@ -680,7 +695,7 @@ static ipc::buff_t recv(ipc::handle_t h, std::uint64_t tm) {
}, r_info};
}
} else {
ipc::log("fail: shm::handle for large message. msg_id: %zd, buf_id: %zd, size: %zd\n", msg.id_, buf_id, msg_size);
log.error("fail: shm::handle for large message. msg_id: ", msg.id_, ", buf_id: ", buf_id, ", size: ", msg_size);
continue;
}
}

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,22 +1,8 @@
#ifndef LIBIPC_SRC_PLATFORM_DETAIL_H_
#define LIBIPC_SRC_PLATFORM_DETAIL_H_
// detect platform
#include "libipc/imp/detect_plat.h"
#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || \
defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || \
defined(WINCE) || defined(_WIN32_WCE)
# define IPC_OS_WINDOWS_
#elif defined(__linux__) || defined(__linux)
# define IPC_OS_LINUX_
#elif defined(__FreeBSD__)
# define IPC_OS_FREEBSD_
#elif defined(__QNX__)
# define IPC_OS_QNX_
#elif defined(__APPLE__)
#elif defined(__ANDROID__)
// TBD
#endif
#if defined(__cplusplus)
@ -29,12 +15,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
@ -44,23 +24,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___); \
@ -130,3 +98,5 @@ constexpr const T& (min)(const T& a, const T& b) {
#endif // defined(__cplusplus)
#endif // LIBIPC_SRC_PLATFORM_DETAIL_H_

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

@ -1,6 +1,6 @@
#pragma once
#include "libipc/utility/log.h"
#include "libipc/imp/log.h"
#include "libipc/mutex.h"
#include "get_wait_time.h"
@ -19,11 +19,12 @@ public:
~condition() = default;
bool wait(ipc::sync::mutex &mtx, std::uint64_t tm) noexcept {
LIBIPC_LOG();
if (!valid()) return false;
if (tm == invalid_value) {
int eno = A0_SYSERR(a0_cnd_wait(native(), static_cast<a0_mtx_t *>(mtx.native())));
if (eno != 0) {
ipc::error("fail condition wait[%d]\n", eno);
log.error("fail condition wait[", eno, "]");
return false;
}
} else {
@ -31,8 +32,7 @@ public:
int eno = A0_SYSERR(a0_cnd_timedwait(native(), static_cast<a0_mtx_t *>(mtx.native()), {ts}));
if (eno != 0) {
if (eno != ETIMEDOUT) {
ipc::error("fail condition timedwait[%d]: tm = %zd, tv_sec = %ld, tv_nsec = %ld\n",
eno, tm, ts.tv_sec, ts.tv_nsec);
log.error("fail condition timedwait[", eno, "]: tm = ", tm, ", tv_sec = ", ts.tv_sec, ", tv_nsec = ", ts.tv_nsec);
}
return false;
}
@ -41,20 +41,22 @@ public:
}
bool notify(ipc::sync::mutex &mtx) noexcept {
LIBIPC_LOG();
if (!valid()) return false;
int eno = A0_SYSERR(a0_cnd_signal(native(), static_cast<a0_mtx_t *>(mtx.native())));
if (eno != 0) {
ipc::error("fail condition notify[%d]\n", eno);
log.error("fail condition notify[", eno, "]");
return false;
}
return true;
}
bool broadcast(ipc::sync::mutex &mtx) noexcept {
LIBIPC_LOG();
if (!valid()) return false;
int eno = A0_SYSERR(a0_cnd_broadcast(native(), static_cast<a0_mtx_t *>(mtx.native())));
if (eno != 0) {
ipc::error("fail condition broadcast[%d]\n", eno);
log.error("fail condition broadcast[", eno, "]");
return false;
}
return true;

View File

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

View File

@ -6,8 +6,8 @@
#include <atomic>
#include "libipc/platform/detail.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/imp/log.h"
#include "libipc/mem/resource.h"
#include "libipc/shm.h"
#include "get_wait_time.h"
@ -23,6 +23,7 @@ namespace sync {
class robust_mutex : public sync::obj_impl<a0_mtx_t> {
public:
bool lock(std::uint64_t tm) noexcept {
LIBIPC_LOG();
if (!valid()) return false;
for (;;) {
auto ts = linux_::detail::make_timespec(tm);
@ -37,24 +38,25 @@ public:
case EOWNERDEAD: {
int eno2 = A0_SYSERR(a0_mtx_consistent(native()));
if (eno2 != 0) {
ipc::error("fail mutex lock[%d] -> consistent[%d]\n", eno, eno2);
log.error("fail mutex lock[", eno, "] -> consistent[", eno2, "]");
return false;
}
int eno3 = A0_SYSERR(a0_mtx_unlock(native()));
if (eno3 != 0) {
ipc::error("fail mutex lock[%d] -> unlock[%d]\n", eno, eno3);
log.error("fail mutex lock[", eno, "] -> unlock[", eno3, "]");
return false;
}
}
break; // loop again
default:
ipc::error("fail mutex lock[%d]\n", eno);
log.error("fail mutex lock[", eno, "]");
return false;
}
}
}
bool try_lock() noexcept(false) {
LIBIPC_LOG();
if (!valid()) return false;
int eno = A0_SYSERR(a0_mtx_timedlock(native(), {linux_::detail::make_timespec(0)}));
switch (eno) {
@ -65,28 +67,29 @@ public:
case EOWNERDEAD: {
int eno2 = A0_SYSERR(a0_mtx_consistent(native()));
if (eno2 != 0) {
ipc::error("fail mutex try_lock[%d] -> consistent[%d]\n", eno, eno2);
log.error("fail mutex try_lock[", eno, "] -> consistent[", eno2, "]");
break;
}
int eno3 = A0_SYSERR(a0_mtx_unlock(native()));
if (eno3 != 0) {
ipc::error("fail mutex try_lock[%d] -> unlock[%d]\n", eno, eno3);
log.error("fail mutex try_lock[", eno, "] -> unlock[", eno3, "]");
break;
}
}
break;
default:
ipc::error("fail mutex try_lock[%d]\n", eno);
log.error("fail mutex try_lock[", eno, "]");
break;
}
throw std::system_error{eno, std::system_category()};
}
bool unlock() noexcept {
LIBIPC_LOG();
if (!valid()) return false;
int eno = A0_SYSERR(a0_mtx_unlock(native()));
if (eno != 0) {
ipc::error("fail mutex unlock[%d]\n", eno);
log.error("fail mutex unlock[", eno, "]");
return false;
}
return true;
@ -108,7 +111,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 +125,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 +139,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,6 +1,6 @@
#pragma once
#include "libipc/utility/log.h"
#include "libipc/imp/log.h"
#include "libipc/shm.h"
#include "a0/empty.h"
@ -19,8 +19,9 @@ protected:
sync_t *h_ = nullptr;
sync_t *acquire_handle(char const *name) {
LIBIPC_LOG();
if (!shm_.acquire(name, sizeof(sync_t))) {
ipc::error("[acquire_handle] fail shm.acquire: %s\n", name);
log.error("[acquire_handle] fail shm.acquire: ", name);
return nullptr;
}
return static_cast<sync_t *>(shm_.get());

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_) || defined(IPC_OS_FREEBSD_)
#elif defined(LIBIPC_OS_QNX) || defined(LIBIPC_OS_FREEBSD)
#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_) || defined(IPC_OS_FREEBSD_)
#elif defined(LIBIPC_OS_LINUX) || defined(LIBIPC_OS_QNX) || defined(LIBIPC_OS_FREEBSD)
#include "libipc/platform/posix/shm_posix.cpp"
#else/*IPC_OS*/
# error "Unsupported platform."

View File

@ -5,7 +5,7 @@
#include <pthread.h>
#include "libipc/utility/log.h"
#include "libipc/imp/log.h"
#include "libipc/utility/scope_guard.h"
#include "libipc/mutex.h"
#include "libipc/shm.h"
@ -21,8 +21,9 @@ class condition {
pthread_cond_t *cond_ = nullptr;
pthread_cond_t *acquire_cond(char const *name) {
LIBIPC_LOG();
if (!shm_.acquire(name, sizeof(pthread_cond_t))) {
ipc::error("[acquire_cond] fail shm.acquire: %s\n", name);
log.error("[acquire_cond] fail shm.acquire: ", name);
return nullptr;
}
return static_cast<pthread_cond_t *>(shm_.get());
@ -47,6 +48,7 @@ public:
}
bool open(char const *name) noexcept {
LIBIPC_LOG();
close();
if ((cond_ = acquire_cond(name)) == nullptr) {
return false;
@ -60,17 +62,17 @@ public:
int eno;
pthread_condattr_t cond_attr;
if ((eno = ::pthread_condattr_init(&cond_attr)) != 0) {
ipc::error("fail pthread_condattr_init[%d]\n", eno);
log.error("fail pthread_condattr_init[", 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);
log.error("fail pthread_condattr_setpshared[", eno, "]");
return false;
}
*cond_ = PTHREAD_COND_INITIALIZER;
if ((eno = ::pthread_cond_init(cond_, &cond_attr)) != 0) {
ipc::error("fail pthread_cond_init[%d]\n", eno);
log.error("fail pthread_cond_init[", eno, "]");
return false;
}
finally.dismiss();
@ -78,10 +80,11 @@ public:
}
void close() noexcept {
LIBIPC_LOG();
if ((shm_.ref() <= 1) && cond_ != nullptr) {
int eno;
if ((eno = ::pthread_cond_destroy(cond_)) != 0) {
ipc::error("fail pthread_cond_destroy[%d]\n", eno);
log.error("fail pthread_cond_destroy[", eno, "]");
}
}
shm_.release();
@ -92,7 +95,7 @@ public:
if ((shm_.ref() <= 1) && cond_ != nullptr) {
int eno;
if ((eno = ::pthread_cond_destroy(cond_)) != 0) {
ipc::error("fail pthread_cond_destroy[%d]\n", eno);
log.error("fail pthread_cond_destroy[", eno, "]");
}
}
shm_.clear(); // Make sure the storage is cleaned up.
@ -104,12 +107,13 @@ public:
}
bool wait(ipc::sync::mutex &mtx, std::uint64_t tm) noexcept {
LIBIPC_LOG();
if (!valid()) return false;
switch (tm) {
case invalid_value: {
int eno;
if ((eno = ::pthread_cond_wait(cond_, static_cast<pthread_mutex_t *>(mtx.native()))) != 0) {
ipc::error("fail pthread_cond_wait[%d]\n", eno);
log.error("fail pthread_cond_wait[", eno, "]");
return false;
}
}
@ -119,8 +123,7 @@ public:
int eno;
if ((eno = ::pthread_cond_timedwait(cond_, static_cast<pthread_mutex_t *>(mtx.native()), &ts)) != 0) {
if (eno != ETIMEDOUT) {
ipc::error("fail pthread_cond_timedwait[%d]: tm = %zd, tv_sec = %ld, tv_nsec = %ld\n",
eno, tm, ts.tv_sec, ts.tv_nsec);
log.error("fail pthread_cond_timedwait[", eno, "]: tm = ", tm, ", tv_sec = ", ts.tv_sec, ", tv_nsec = ", ts.tv_nsec);
}
return false;
}
@ -134,7 +137,7 @@ public:
if (!valid()) return false;
int eno;
if ((eno = ::pthread_cond_signal(cond_)) != 0) {
ipc::error("fail pthread_cond_signal[%d]\n", eno);
log.error("fail pthread_cond_signal[", eno, "]");
return false;
}
return true;
@ -144,7 +147,7 @@ public:
if (!valid()) return false;
int eno;
if ((eno = ::pthread_cond_broadcast(cond_)) != 0) {
ipc::error("fail pthread_cond_broadcast[%d]\n", eno);
log.error("fail pthread_cond_broadcast[", eno, "]");
return false;
}
return true;

View File

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

View File

@ -10,9 +10,9 @@
#include <pthread.h>
#include "libipc/platform/detail.h"
#include "libipc/utility/log.h"
#include "libipc/imp/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;
@ -127,21 +127,21 @@ public:
int eno;
pthread_mutexattr_t mutex_attr;
if ((eno = ::pthread_mutexattr_init(&mutex_attr)) != 0) {
ipc::error("fail pthread_mutexattr_init[%d]\n", eno);
log.error("fail pthread_mutexattr_init[", 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);
log.error("fail pthread_mutexattr_setpshared[", eno, "]");
return false;
}
if ((eno = ::pthread_mutexattr_setrobust(&mutex_attr, PTHREAD_MUTEX_ROBUST)) != 0) {
ipc::error("fail pthread_mutexattr_setrobust[%d]\n", eno);
log.error("fail pthread_mutexattr_setrobust[", eno, "]");
return false;
}
*mutex_ = PTHREAD_MUTEX_INITIALIZER;
if ((eno = ::pthread_mutex_init(mutex_, &mutex_attr)) != 0) {
ipc::error("fail pthread_mutex_init[%d]\n", eno);
log.error("fail pthread_mutex_init[", eno, "]");
return false;
}
finally.dismiss();
@ -149,6 +149,7 @@ public:
}
void close() noexcept {
LIBIPC_LOG();
if ((ref_ != nullptr) && (shm_ != nullptr) && (mutex_ != nullptr)) {
if (shm_->name() != nullptr) {
release_mutex(shm_->name(), [this] {
@ -165,7 +166,7 @@ public:
int eno;
if ((eno = ::pthread_mutex_destroy(mutex_)) != 0) {
ipc::error("fail pthread_mutex_destroy[%d]\n", eno);
log.error("fail pthread_mutex_destroy[", eno, "]");
}
return true;
}
@ -179,6 +180,7 @@ public:
}
void clear() noexcept {
LIBIPC_LOG();
if ((shm_ != nullptr) && (mutex_ != nullptr)) {
if (shm_->name() != nullptr) {
release_mutex(shm_->name(), [this] {
@ -187,7 +189,7 @@ public:
int eno;
if ((eno = ::pthread_mutex_destroy(mutex_)) != 0) {
ipc::error("fail pthread_mutex_destroy[%d]\n", eno);
log.error("fail pthread_mutex_destroy[", eno, "]");
}
shm_->clear();
return true;
@ -206,6 +208,7 @@ public:
}
bool lock(std::uint64_t tm) noexcept {
LIBIPC_LOG();
if (!valid()) return false;
for (;;) {
auto ts = posix_::detail::make_timespec(tm);
@ -222,7 +225,7 @@ public:
// but the previous owner died. We need to make it consistent.
int eno2 = ::pthread_mutex_consistent(mutex_);
if (eno2 != 0) {
ipc::error("fail pthread_mutex_lock[%d], pthread_mutex_consistent[%d]\n", eno, eno2);
log.error("fail pthread_mutex_lock[", eno, "], pthread_mutex_consistent[", eno2, "]");
return false;
}
// After calling pthread_mutex_consistent(), the mutex is now in a
@ -230,7 +233,7 @@ public:
return true;
}
default:
ipc::error("fail pthread_mutex_lock[%d]\n", eno);
log.error("fail pthread_mutex_lock[", eno, "]");
return false;
}
}
@ -250,7 +253,7 @@ public:
// but the previous owner died. We need to make it consistent.
int eno2 = ::pthread_mutex_consistent(mutex_);
if (eno2 != 0) {
ipc::error("fail pthread_mutex_timedlock[%d], pthread_mutex_consistent[%d]\n", eno, eno2);
log.error("fail pthread_mutex_timedlock[", eno, "], pthread_mutex_consistent[", eno2, "]");
throw std::system_error{eno2, std::system_category()};
}
// After calling pthread_mutex_consistent(), the mutex is now in a
@ -258,17 +261,18 @@ public:
return true;
}
default:
ipc::error("fail pthread_mutex_timedlock[%d]\n", eno);
log.error("fail pthread_mutex_timedlock[", eno, "]");
break;
}
throw std::system_error{eno, std::system_category()};
}
bool unlock() noexcept {
LIBIPC_LOG();
if (!valid()) return false;
int eno;
if ((eno = ::pthread_mutex_unlock(mutex_)) != 0) {
ipc::error("fail pthread_mutex_unlock[%d]\n", eno);
log.error("fail pthread_mutex_unlock[", eno, "]");
return false;
}
return true;

View File

@ -7,7 +7,7 @@
#include <semaphore.h>
#include <errno.h>
#include "libipc/utility/log.h"
#include "libipc/imp/log.h"
#include "libipc/shm.h"
#include "get_wait_time.h"
@ -34,9 +34,10 @@ public:
}
bool open(char const *name, std::uint32_t count) noexcept {
LIBIPC_LOG();
close();
if (!shm_.acquire(name, 1)) {
ipc::error("[open_semaphore] fail shm.acquire: %s\n", name);
log.error("[open_semaphore] fail shm.acquire: ", name);
return false;
}
// POSIX semaphore names must start with "/" on some platforms (e.g., FreeBSD)
@ -48,22 +49,23 @@ public:
}
h_ = ::sem_open(sem_name_.c_str(), O_CREAT, 0666, static_cast<unsigned>(count));
if (h_ == SEM_FAILED) {
ipc::error("fail sem_open[%d]: %s\n", errno, sem_name_.c_str());
log.error("fail sem_open[", errno, "]: ", sem_name_);
return false;
}
return true;
}
void close() noexcept {
LIBIPC_LOG();
if (!valid()) return;
if (::sem_close(h_) != 0) {
ipc::error("fail sem_close[%d]: %s\n", errno);
log.error("fail sem_close[", errno, "]");
}
h_ = SEM_FAILED;
if (!sem_name_.empty() && shm_.name() != nullptr) {
if (shm_.release() <= 1) {
if (::sem_unlink(sem_name_.c_str()) != 0) {
ipc::error("fail sem_unlink[%d]: %s, name: %s\n", errno, sem_name_.c_str());
log.error("fail sem_unlink[", errno, "]: ", sem_name_);
}
}
}
@ -71,9 +73,10 @@ public:
}
void clear() noexcept {
LIBIPC_LOG();
if (valid()) {
if (::sem_close(h_) != 0) {
ipc::error("fail sem_close[%d]: %s\n", errno);
log.error("fail sem_close[", errno, "]");
}
h_ = SEM_FAILED;
}
@ -97,18 +100,18 @@ public:
}
bool wait(std::uint64_t tm) noexcept {
LIBIPC_LOG();
if (!valid()) return false;
if (tm == invalid_value) {
if (::sem_wait(h_) != 0) {
ipc::error("fail sem_wait[%d]: %s\n", errno);
log.error("fail sem_wait[", errno, "]");
return false;
}
} else {
auto ts = posix_::detail::make_timespec(tm);
if (::sem_timedwait(h_, &ts) != 0) {
if (errno != ETIMEDOUT) {
ipc::error("fail sem_timedwait[%d]: tm = %zd, tv_sec = %ld, tv_nsec = %ld\n",
errno, tm, ts.tv_sec, ts.tv_nsec);
log.error("fail sem_timedwait[", errno, "]: tm = ", tm, ", tv_sec = ", ts.tv_sec, ", tv_nsec = ", ts.tv_nsec);
}
return false;
}
@ -117,10 +120,11 @@ public:
}
bool post(std::uint32_t count) noexcept {
LIBIPC_LOG();
if (!valid()) return false;
for (std::uint32_t i = 0; i < count; ++i) {
if (::sem_post(h_) != 0) {
ipc::error("fail sem_post[%d]: %s\n", errno);
log.error("fail sem_post[", errno, "]");
return false;
}
}

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/imp/log.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) {
@ -45,17 +45,18 @@ namespace ipc {
namespace shm {
id_t acquire(char const * name, std::size_t size, unsigned mode) {
LIBIPC_LOG();
if (!is_valid_string(name)) {
ipc::error("fail acquire: name is empty\n");
log.error("fail acquire: name is empty");
return nullptr;
}
// For portable use, a shared memory object should be identified by name of the form /somename.
// see: https://man7.org/linux/man-pages/man3/shm_open.3.html
ipc::string op_name;
std::string op_name;
if (name[0] == '/') {
op_name = name;
} else {
op_name = ipc::string{"/"} + name;
op_name = std::string{"/"} + name;
}
// Open the object for read-write access.
int flag = O_RDWR;
@ -79,14 +80,14 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
if (fd == -1) {
// only open shm not log error when file not exist
if (open != mode || ENOENT != errno) {
ipc::error("fail shm_open[%d]: %s\n", errno, op_name.c_str());
log.error("fail shm_open[", errno, "]: ", op_name);
}
return nullptr;
}
::fchmod(fd, S_IRUSR | S_IWUSR |
S_IRGRP | S_IWGRP |
S_IROTH | S_IWOTH);
auto ii = mem::alloc<id_info_t>();
auto ii = mem::$new<id_info_t>();
ii->fd_ = fd;
ii->size_ = size;
ii->name_ = std::move(op_name);
@ -105,21 +106,23 @@ std::int32_t get_ref(id_t id) {
}
void sub_ref(id_t id) {
LIBIPC_LOG();
if (id == nullptr) {
ipc::error("fail sub_ref: invalid id (null)\n");
log.error("fail sub_ref: invalid id (null)");
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_);
log.error("fail sub_ref: invalid id (mem = ", ii->mem_, ", size = ", ii->size_, ")");
return;
}
acc_of(ii->mem_, ii->size_).fetch_sub(1, std::memory_order_acq_rel);
}
void * get_mem(id_t id, std::size_t * size) {
LIBIPC_LOG();
if (id == nullptr) {
ipc::error("fail get_mem: invalid id (null)\n");
log.error("fail get_mem: invalid id (null)");
return nullptr;
}
auto ii = static_cast<id_info_t*>(id);
@ -129,31 +132,31 @@ void * get_mem(id_t id, std::size_t * size) {
}
int fd = ii->fd_;
if (fd == -1) {
ipc::error("fail get_mem: invalid id (fd = -1)\n");
log.error("fail get_mem: invalid id (fd = -1)");
return nullptr;
}
if (ii->size_ == 0) {
struct stat st;
if (::fstat(fd, &st) != 0) {
ipc::error("fail fstat[%d]: %s, size = %zd\n", errno, ii->name_.c_str(), ii->size_);
log.error("fail fstat[", errno, "]: ", ii->name_, ", size = ", ii->size_);
return nullptr;
}
ii->size_ = static_cast<std::size_t>(st.st_size);
if ((ii->size_ <= sizeof(info_t)) || (ii->size_ % sizeof(info_t))) {
ipc::error("fail get_mem: %s, invalid size = %zd\n", ii->name_.c_str(), ii->size_);
log.error("fail get_mem: ", ii->name_, ", invalid size = ", ii->size_);
return nullptr;
}
}
else {
ii->size_ = calc_size(ii->size_);
if (::ftruncate(fd, static_cast<off_t>(ii->size_)) != 0) {
ipc::error("fail ftruncate[%d]: %s, size = %zd\n", errno, ii->name_.c_str(), ii->size_);
log.error("fail ftruncate[", errno, "]: ", ii->name_, ", size = ", ii->size_);
return nullptr;
}
}
void* mem = ::mmap(nullptr, ii->size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (mem == MAP_FAILED) {
ipc::error("fail mmap[%d]: %s, size = %zd\n", errno, ii->name_.c_str(), ii->size_);
log.error("fail mmap[", errno, "]: ", ii->name_, ", size = ", ii->size_);
return nullptr;
}
::close(fd);
@ -165,33 +168,34 @@ void * get_mem(id_t id, std::size_t * size) {
}
std::int32_t release(id_t id) noexcept {
LIBIPC_LOG();
if (id == nullptr) {
ipc::error("fail release: invalid id (null)\n");
log.error("fail release: invalid id (null)");
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), name = %s\n",
ii->mem_, ii->size_, ii->name_.c_str());
log.error("fail release: invalid id (mem = ", ii->mem_, ", size = ", ii->size_, "), name = ", ii->name_);
}
else if ((ret = acc_of(ii->mem_, ii->size_).fetch_sub(1, std::memory_order_acq_rel)) <= 1) {
::munmap(ii->mem_, ii->size_);
if (!ii->name_.empty()) {
int unlink_ret = ::shm_unlink(ii->name_.c_str());
if (unlink_ret == -1) {
ipc::error("fail shm_unlink[%d]: %s\n", errno, ii->name_.c_str());
log.error("fail shm_unlink[", errno, "]: ", ii->name_);
}
}
}
else ::munmap(ii->mem_, ii->size_);
mem::free(ii);
mem::$delete(ii);
return ret;
}
void remove(id_t id) noexcept {
LIBIPC_LOG();
if (id == nullptr) {
ipc::error("fail remove: invalid id (null)\n");
log.error("fail remove: invalid id (null)");
return;
}
auto ii = static_cast<id_info_t*>(id);
@ -200,26 +204,27 @@ void remove(id_t id) noexcept {
if (!name.empty()) {
int unlink_ret = ::shm_unlink(name.c_str());
if (unlink_ret == -1) {
ipc::error("fail shm_unlink[%d]: %s\n", errno, name.c_str());
log.error("fail shm_unlink[", errno, "]: ", name);
}
}
}
void remove(char const * name) noexcept {
LIBIPC_LOG();
if (!is_valid_string(name)) {
ipc::error("fail remove: name is empty\n");
log.error("fail remove: name is empty");
return;
}
// For portable use, a shared memory object should be identified by name of the form /somename.
ipc::string op_name;
std::string op_name;
if (name[0] == '/') {
op_name = name;
} else {
op_name = ipc::string{"/"} + name;
op_name = std::string{"/"} + name;
}
int unlink_ret = ::shm_unlink(op_name.c_str());
if (unlink_ret == -1) {
ipc::error("fail shm_unlink[%d]: %s\n", errno, op_name.c_str());
log.error("fail shm_unlink[", errno, "]: ", op_name);
}
}

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

@ -10,7 +10,7 @@
#include <Windows.h>
#endif
#include "libipc/utility/log.h"
#include "libipc/imp/log.h"
#include "libipc/utility/scope_guard.h"
#include "libipc/platform/detail.h"
#include "libipc/mutex.h"
@ -85,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);
@ -97,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

@ -5,29 +5,31 @@
namespace ipc {
namespace detail {
inline LPSECURITY_ATTRIBUTES get_sa() {
static struct initiator {
struct sa_initiator {
SECURITY_DESCRIPTOR sd_;
SECURITY_ATTRIBUTES sa_;
bool succ_ = false;
SECURITY_DESCRIPTOR sd_;
SECURITY_ATTRIBUTES sa_;
bool succ_ = false;
initiator() {
if (!::InitializeSecurityDescriptor(&sd_, SECURITY_DESCRIPTOR_REVISION)) {
ipc::error("fail InitializeSecurityDescriptor[%d]\n", static_cast<int>(::GetLastError()));
return;
}
if (!::SetSecurityDescriptorDacl(&sd_, TRUE, NULL, FALSE)) {
ipc::error("fail SetSecurityDescriptorDacl[%d]\n", static_cast<int>(::GetLastError()));
return;
}
sa_.nLength = sizeof(SECURITY_ATTRIBUTES);
sa_.bInheritHandle = FALSE;
sa_.lpSecurityDescriptor = &sd_;
succ_ = true;
template <typename Logger>
sa_initiator(Logger const &log) {
if (!::InitializeSecurityDescriptor(&sd_, SECURITY_DESCRIPTOR_REVISION)) {
log.error("fail InitializeSecurityDescriptor[", static_cast<int>(::GetLastError()), "]");
return;
}
} handle;
if (!::SetSecurityDescriptorDacl(&sd_, TRUE, NULL, FALSE)) {
log.error("fail SetSecurityDescriptorDacl[", static_cast<int>(::GetLastError()), "]");
return;
}
sa_.nLength = sizeof(SECURITY_ATTRIBUTES);
sa_.bInheritHandle = FALSE;
sa_.lpSecurityDescriptor = &sd_;
succ_ = true;
}
};
inline LPSECURITY_ATTRIBUTES get_sa() {
LIBIPC_LOG();
static sa_initiator handle(log);
return handle.succ_ ? &handle.sa_ : nullptr;
}

View File

@ -9,7 +9,7 @@
#include <Windows.h>
#endif
#include "libipc/utility/log.h"
#include "libipc/imp/log.h"
#include "to_tchar.h"
#include "get_sa.h"
@ -36,10 +36,11 @@ public:
}
bool open(char const *name) noexcept {
LIBIPC_LOG();
close();
h_ = ::CreateMutex(detail::get_sa(), FALSE, detail::to_tchar(name).c_str());
if (h_ == NULL) {
ipc::error("fail CreateMutex[%lu]: %s\n", ::GetLastError(), name);
log.error("fail CreateMutex[", static_cast<unsigned long>(::GetLastError()), "]: ", name);
return false;
}
return true;
@ -55,10 +56,11 @@ 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 {
LIBIPC_LOG();
DWORD ret, ms = (tm == invalid_value) ? INFINITE : static_cast<DWORD>(tm);
for(;;) {
switch ((ret = ::WaitForSingleObject(h_, ms))) {
@ -67,19 +69,20 @@ public:
case WAIT_TIMEOUT:
return false;
case WAIT_ABANDONED:
ipc::log("fail WaitForSingleObject[%lu]: WAIT_ABANDONED, try again.\n", ::GetLastError());
log.warning("fail WaitForSingleObject[", ::GetLastError(), "]: WAIT_ABANDONED, try again.");
if (!unlock()) {
return false;
}
break; // loop again
default:
ipc::error("fail WaitForSingleObject[%lu]: 0x%08X\n", ::GetLastError(), ret);
log.error("fail WaitForSingleObject[", ::GetLastError(), "]: 0x", std::hex, ret, std::dec);
return false;
}
}
}
bool try_lock() noexcept(false) {
LIBIPC_LOG();
DWORD ret = ::WaitForSingleObject(h_, 0);
switch (ret) {
case WAIT_OBJECT_0:
@ -88,16 +91,17 @@ public:
return false;
case WAIT_ABANDONED:
unlock();
IPC_FALLTHROUGH_;
LIBIPC_FALLTHROUGH;
default:
ipc::error("fail WaitForSingleObject[%lu]: 0x%08X\n", ::GetLastError(), ret);
log.error("fail WaitForSingleObject[", ::GetLastError(), "]: 0x", std::hex, ret, std::dec);
throw std::system_error{static_cast<int>(ret), std::system_category()};
}
}
bool unlock() noexcept {
LIBIPC_LOG();
if (!::ReleaseMutex(h_)) {
ipc::error("fail ReleaseMutex[%lu]\n", ::GetLastError());
log.error("fail ReleaseMutex[", ::GetLastError(), "]");
return false;
}
return true;

View File

@ -8,7 +8,7 @@
#include <Windows.h>
#endif
#include "libipc/utility/log.h"
#include "libipc/imp/log.h"
#include "to_tchar.h"
#include "get_sa.h"
@ -33,12 +33,13 @@ public:
}
bool open(char const *name, std::uint32_t count) noexcept {
LIBIPC_LOG();
close();
h_ = ::CreateSemaphore(detail::get_sa(),
static_cast<LONG>(count), LONG_MAX,
detail::to_tchar(name).c_str());
if (h_ == NULL) {
ipc::error("fail CreateSemaphore[%lu]: %s\n", ::GetLastError(), name);
log.error("fail CreateSemaphore[", static_cast<unsigned long>(::GetLastError()), "]: ", name);
return false;
}
return true;
@ -54,10 +55,11 @@ 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 {
LIBIPC_LOG();
DWORD ret, ms = (tm == invalid_value) ? INFINITE : static_cast<DWORD>(tm);
switch ((ret = ::WaitForSingleObject(h_, ms))) {
case WAIT_OBJECT_0:
@ -66,14 +68,15 @@ public:
return false;
case WAIT_ABANDONED:
default:
ipc::error("fail WaitForSingleObject[%lu]: 0x%08X\n", ::GetLastError(), ret);
log.error("fail WaitForSingleObject[", ::GetLastError(), "]: 0x", std::hex, ret, std::dec);
return false;
}
}
bool post(std::uint32_t count) noexcept {
LIBIPC_LOG();
if (!::ReleaseSemaphore(h_, static_cast<LONG>(count), NULL)) {
ipc::error("fail ReleaseSemaphore[%lu]\n", ::GetLastError());
log.error("fail ReleaseSemaphore[", ::GetLastError(), "]");
return false;
}
return true;

View File

@ -11,10 +11,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/imp/log.h"
#include "libipc/mem/resource.h"
#include "libipc/mem/new.h"
#include "to_tchar.h"
#include "get_sa.h"
@ -45,8 +45,9 @@ namespace ipc {
namespace shm {
id_t acquire(char const * name, std::size_t size, unsigned mode) {
LIBIPC_LOG();
if (!is_valid_string(name)) {
ipc::error("fail acquire: name is empty\n");
log.error("fail acquire: name is empty");
return nullptr;
}
HANDLE h;
@ -55,7 +56,7 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
if (mode == open) {
h = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, fmt_name.c_str());
if (h == NULL) {
ipc::error("fail OpenFileMapping[%d]: %s\n", static_cast<int>(::GetLastError()), name);
log.error("fail OpenFileMapping[", static_cast<int>(::GetLastError()), "]: ", name);
return nullptr;
}
}
@ -72,11 +73,11 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
h = NULL;
}
if (h == NULL) {
ipc::error("fail CreateFileMapping[%d]: %s\n", static_cast<int>(err), name);
log.error("fail CreateFileMapping[", static_cast<int>(err), "]: ", name);
return nullptr;
}
}
auto ii = mem::alloc<id_info_t>();
auto ii = mem::$new<id_info_t>();
ii->h_ = h;
ii->size_ = size;
return ii;
@ -94,21 +95,23 @@ std::int32_t get_ref(id_t id) {
}
void sub_ref(id_t id) {
LIBIPC_LOG();
if (id == nullptr) {
ipc::error("fail sub_ref: invalid id (null)\n");
log.error("fail sub_ref: invalid id (null)");
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_);
log.error("fail sub_ref: invalid id (mem = ", ii->mem_, ", size = ", 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) {
LIBIPC_LOG();
if (id == nullptr) {
ipc::error("fail get_mem: invalid id (null)\n");
log.error("fail get_mem: invalid id (null)");
return nullptr;
}
auto ii = static_cast<id_info_t*>(id);
@ -117,17 +120,17 @@ void * get_mem(id_t id, std::size_t * size) {
return ii->mem_;
}
if (ii->h_ == NULL) {
ipc::error("fail to_mem: invalid id (h = null)\n");
log.error("fail to_mem: invalid id (h = null)");
return nullptr;
}
LPVOID mem = ::MapViewOfFile(ii->h_, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (mem == NULL) {
ipc::error("fail MapViewOfFile[%d]\n", static_cast<int>(::GetLastError()));
log.error("fail MapViewOfFile[", static_cast<int>(::GetLastError()), "]");
return nullptr;
}
MEMORY_BASIC_INFORMATION mem_info;
if (::VirtualQuery(mem, &mem_info, sizeof(mem_info)) == 0) {
ipc::error("fail VirtualQuery[%d]\n", static_cast<int>(::GetLastError()));
log.error("fail VirtualQuery[", static_cast<int>(::GetLastError()), "]");
return nullptr;
}
std::size_t actual_size = static_cast<std::size_t>(mem_info.RegionSize);
@ -144,38 +147,41 @@ void * get_mem(id_t id, std::size_t * size) {
}
std::int32_t release(id_t id) noexcept {
LIBIPC_LOG();
if (id == nullptr) {
ipc::error("fail release: invalid id (null)\n");
log.error("fail release: invalid id (null)");
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_);
log.error("fail release: invalid id (mem = ", ii->mem_, ", size = ", ii->size_, ")");
}
else {
ret = acc_of(ii->mem_, calc_size(ii->size_)).fetch_sub(1, std::memory_order_acq_rel);
::UnmapViewOfFile(static_cast<LPCVOID>(ii->mem_));
}
if (ii->h_ == NULL) {
ipc::error("fail release: invalid id (h = null)\n");
log.error("fail release: invalid id (h = null)");
}
else ::CloseHandle(ii->h_);
mem::free(ii);
mem::$delete(ii);
return ret;
}
void remove(id_t id) noexcept {
LIBIPC_LOG();
if (id == nullptr) {
ipc::error("fail release: invalid id (null)\n");
log.error("fail release: invalid id (null)");
return;
}
release(id);
}
void remove(char const * name) noexcept {
LIBIPC_LOG();
if (!is_valid_string(name)) {
ipc::error("fail remove: name is empty\n");
log.error("fail remove: name is empty");
return;
}
// Do Nothing.
@ -183,3 +189,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

@ -15,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 {
@ -40,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
}
@ -52,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
}
@ -69,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

@ -10,7 +10,7 @@
#include "libipc/platform/detail.h"
#include "libipc/circ/elem_def.h"
#include "libipc/utility/log.h"
#include "libipc/imp/log.h"
#include "libipc/utility/utility.h"
namespace ipc {
@ -242,6 +242,7 @@ struct prod_cons_impl<wr<relat::single, relat::multi, trans::broadcast>> {
template <typename W, typename F, typename E>
bool force_push(W* wrapper, F&& f, E* elems) {
LIBIPC_LOG();
E* el;
epoch_ += ep_incr;
for (unsigned k = 0;;) {
@ -252,7 +253,7 @@ struct prod_cons_impl<wr<relat::single, relat::multi, trans::broadcast>> {
auto cur_rc = el->rc_.load(std::memory_order_acquire);
circ::cc_t rem_cc = cur_rc & ep_mask;
if (cc & rem_cc) {
ipc::log("force_push: k = %u, cc = %u, rem_cc = %u\n", k, cc, rem_cc);
log.debug("force_push: k = ", k, ", cc = ", cc, ", rem_cc = ", rem_cc);
cc = wrapper->elems()->disconnect_receiver(rem_cc); // disconnect all invalid readers
if (cc == 0) return false; // no reader
}
@ -364,6 +365,7 @@ struct prod_cons_impl<wr<relat::multi, relat::multi, trans::broadcast>> {
template <typename W, typename F, typename E>
bool force_push(W* wrapper, F&& f, E* elems) {
LIBIPC_LOG();
E* el;
circ::u2_t cur_ct;
rc_t epoch = epoch_.fetch_add(ep_incr, std::memory_order_release) + ep_incr;
@ -375,7 +377,7 @@ struct prod_cons_impl<wr<relat::multi, relat::multi, trans::broadcast>> {
auto cur_rc = el->rc_.load(std::memory_order_acquire);
circ::cc_t rem_cc = cur_rc & rc_mask;
if (cc & rem_cc) {
ipc::log("force_push: k = %u, cc = %u, rem_cc = %u\n", k, cc, rem_cc);
log.debug("force_push: k = ", k, ", cc = ", cc, ", rem_cc = ", rem_cc);
cc = wrapper->elems()->disconnect_receiver(rem_cc); // disconnect all invalid readers
if (cc == 0) return false; // no reader
}

View File

@ -15,10 +15,10 @@
#include "libipc/shm.h"
#include "libipc/rw_lock.h"
#include "libipc/utility/log.h"
#include "libipc/imp/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 {
@ -30,8 +30,9 @@ protected:
template <typename Elems>
Elems* open(char const * name) {
LIBIPC_LOG();
if (!is_valid_string(name)) {
ipc::error("fail open waiter: name is empty!\n");
log.error("fail open waiter: name is empty!");
return nullptr;
}
if (!elems_h_.acquire(name, sizeof(Elems))) {
@ -39,7 +40,7 @@ protected:
}
auto elems = static_cast<Elems*>(elems_h_.get());
if (elems == nullptr) {
ipc::error("fail acquire elems: %s\n", name);
log.error("fail acquire elems: ", name);
return nullptr;
}
elems->init();

View File

@ -5,8 +5,8 @@
#include "libipc/shm.h"
#include "libipc/utility/pimpl.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/imp/log.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;
};
@ -69,12 +69,13 @@ void handle::sub_ref() noexcept {
}
bool handle::acquire(char const * name, std::size_t size, unsigned mode) {
LIBIPC_LOG();
if (!is_valid_string(name)) {
ipc::error("fail acquire: name is empty\n");
log.error("fail acquire: name is empty");
return false;
}
if (size == 0) {
ipc::error("fail acquire: size is 0\n");
log.error("fail acquire: size is 0");
return false;
}
release();

View File

@ -2,14 +2,14 @@
#include "libipc/condition.h"
#include "libipc/utility/pimpl.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/imp/log.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_) || defined(IPC_OS_FREEBSD_)
#elif defined(LIBIPC_OS_QNX) || defined(LIBIPC_OS_FREEBSD)
#include "libipc/platform/posix/condition.h"
#else/*IPC_OS*/
# error "Unsupported platform."
@ -50,8 +50,9 @@ bool condition::valid() const noexcept {
}
bool condition::open(char const *name) noexcept {
LIBIPC_LOG();
if (!is_valid_string(name)) {
ipc::error("fail condition open: name is empty\n");
log.error("fail condition open: name is empty");
return false;
}
return impl(p_)->cond_.open(name);

View File

@ -2,14 +2,14 @@
#include "libipc/mutex.h"
#include "libipc/utility/pimpl.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/imp/log.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_) || defined(IPC_OS_FREEBSD_)
#elif defined(LIBIPC_OS_QNX) || defined(LIBIPC_OS_FREEBSD)
#include "libipc/platform/posix/mutex.h"
#else/*IPC_OS*/
# error "Unsupported platform."
@ -50,8 +50,9 @@ bool mutex::valid() const noexcept {
}
bool mutex::open(char const *name) noexcept {
LIBIPC_LOG();
if (!is_valid_string(name)) {
ipc::error("fail mutex open: name is empty\n");
log.error("fail mutex open: name is empty");
return false;
}
return impl(p_)->lock_.open(name);

View File

@ -2,12 +2,13 @@
#include "libipc/semaphore.h"
#include "libipc/utility/pimpl.h"
#include "libipc/utility/log.h"
#include "libipc/memory/resource.h"
#include "libipc/imp/log.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_) || defined(IPC_OS_FREEBSD_)
#elif defined(LIBIPC_OS_QNX) || defined(LIBIPC_OS_FREEBSD)
#elif defined(LIBIPC_OS_LINUX) || defined(LIBIPC_OS_QNX)
#include "libipc/platform/posix/semaphore_impl.h"
#else/*IPC_OS*/
# error "Unsupported platform."
@ -48,8 +49,9 @@ bool semaphore::valid() const noexcept {
}
bool semaphore::open(char const *name, std::uint32_t count) noexcept {
LIBIPC_LOG();
if (!is_valid_string(name)) {
ipc::error("fail semaphore open: name is empty\n");
log.error("fail semaphore open: name is empty");
return false;
}
return impl(p_)->sem_.open(name, count);

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_) || defined(IPC_OS_FREEBSD_)
#elif defined(LIBIPC_OS_QNX) || defined(LIBIPC_OS_FREEBSD)
#include "libipc/platform/posix/mutex.h"
#else/*IPC_OS*/
# error "Unsupported platform."

View File

@ -1,39 +0,0 @@
#pragma once
#include <cstdio>
#include <utility>
namespace ipc {
namespace detail {
template <typename O>
void print(O out, char const * str) {
std::fprintf(out, "%s", str);
}
template <typename O, typename P1, typename... P>
void print(O out, char const * fmt, P1&& p1, P&&... params) {
std::fprintf(out, fmt, std::forward<P1>(p1), std::forward<P>(params)...);
}
} // namespace detail
inline void log(char const * fmt) {
ipc::detail::print(stdout, fmt);
}
template <typename P1, typename... P>
void log(char const * fmt, P1&& p1, P&&... params) {
ipc::detail::print(stdout, fmt, std::forward<P1>(p1), std::forward<P>(params)...);
}
inline void error(char const * str) {
ipc::detail::print(stderr, str);
}
template <typename P1, typename... P>
void error(char const * fmt, P1&& p1, P&&... params) {
ipc::detail::print(stderr, fmt, std::forward<P1>(p1), std::forward<P>(params)...);
}
} // namespace ipc

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

View File

@ -18,6 +18,10 @@ include_directories(
# Collect only new test files (exclude archive directory)
file(GLOB SRC_FILES
${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/test_*.h)

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"
@ -83,21 +84,34 @@ void test_basic(char const * name) {
EXPECT_EQ(que2.recv(), test2);
}
class data_set {
std::vector<rand_buf> datas_;
public:
data_set() {
datas_.resize(LoopCount);
for (int i = 0; i < LoopCount; ++i) {
datas_[i].set_id(i);
}
}
std::vector<rand_buf> const &get() const noexcept {
return datas_;
}
} const data_set__;
class data_set {
alignas(rand_buf) char datas_[sizeof(rand_buf[LoopCount])];
rand_buf *d_;
public:
data_set() {
// 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) {
::new(d_ + i) rand_buf;
d_[i].set_id(i);
}
}
~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__;
template <relat Rp, relat Rc, trans Ts, typename Que = chan<Rp, Rc, Ts>>
void test_sr(char const * name, int s_cnt, int r_cnt) {
@ -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

@ -1,218 +0,0 @@
#include <vector>
#include <array>
#include <thread>
#include <atomic>
#include <cstddef>
#include "capo/random.hpp"
#include "libipc/memory/resource.h"
#include "libipc/pool_alloc.h"
// #include "gperftools/tcmalloc.h"
#include "test.h"
#include "thread_pool.h"
namespace {
constexpr int DataMin = 4;
constexpr int DataMax = 256;
constexpr int LoopCount = 8388608;
constexpr int ThreadMax = 8;
// constexpr int DataMin = 256;
// constexpr int DataMax = 512;
// constexpr int LoopCount = 2097152;
std::vector<std::size_t> sizes__;
std::vector<void*> ptr_cache__[ThreadMax];
template <typename M>
struct alloc_ix_t {
static std::vector<int> ix_;
static bool inited_;
alloc_ix_t() {
if (inited_) return;
inited_ = true;
M::init();
}
template <int ThreadsN>
static int index(std::size_t /*pid*/, std::size_t /*k*/, std::size_t n) {
return ix_[n];
}
};
template <typename M>
std::vector<int> alloc_ix_t<M>::ix_(LoopCount);
template <typename M>
bool alloc_ix_t<M>::inited_ = false;
struct alloc_FIFO : alloc_ix_t<alloc_FIFO> {
static void init() {
for (int i = 0; i < LoopCount; ++i) {
ix_[static_cast<std::size_t>(i)] = i;
}
}
};
struct alloc_LIFO : alloc_ix_t<alloc_LIFO> {
static void init() {
for (int i = 0; i < LoopCount; ++i) {
ix_[static_cast<std::size_t>(i)] = i;
}
}
template <int ThreadsN>
static int index(std::size_t pid, std::size_t k, std::size_t n) {
constexpr static int CacheSize = LoopCount / ThreadsN;
if (k) {
return ix_[(CacheSize * (2 * pid + 1)) - 1 - n];
}
else return ix_[n];
}
};
struct alloc_Random : alloc_ix_t<alloc_Random> {
static void init() {
capo::random<> rdm_index(0, LoopCount - 1);
for (int i = 0; i < LoopCount; ++i) {
ix_[static_cast<std::size_t>(i)] = rdm_index();
}
}
};
struct Init {
Init() {
capo::random<> rdm{ DataMin, DataMax };
for (int i = 0; i < LoopCount; ++i) {
sizes__.emplace_back(static_cast<std::size_t>(rdm()));
}
for (auto& vec : ptr_cache__) {
vec.resize(LoopCount, nullptr);
}
}
} init__;
template <typename AllocT, int ThreadsN>
void benchmark_alloc(char const * message) {
std::string msg = std::to_string(ThreadsN) + "\t" + message;
constexpr static int CacheSize = LoopCount / ThreadsN;
ipc_ut::sender().start(static_cast<std::size_t>(ThreadsN));
ipc_ut::test_stopwatch sw;
for (int pid = 0; pid < ThreadsN; ++pid) {
ipc_ut::sender() << [&, pid] {
sw.start();
for (int n = (CacheSize * pid); n < (CacheSize * (pid + 1)); ++n) {
std::size_t s = sizes__[n];
AllocT::free(AllocT::alloc(s), s);
}
};
}
ipc_ut::sender().wait_for_done();
sw.print_elapsed<1>(DataMin, DataMax, LoopCount, msg.c_str());
}
template <typename AllocT, typename ModeT, int ThreadsN>
void benchmark_alloc(char const * message) {
std::string msg = std::to_string(ThreadsN) + "\t" + message;
constexpr static int CacheSize = LoopCount / ThreadsN;
ModeT mode;
ipc_ut::sender().start(static_cast<std::size_t>(ThreadsN));
ipc_ut::test_stopwatch sw;
for (int pid = 0; pid < ThreadsN; ++pid) {
ipc_ut::sender() << [&, pid] {
auto& vec = ptr_cache__[pid];
sw.start();
for (std::size_t k = 0; k < 2; ++k)
for (int n = (CacheSize * pid); n < (CacheSize * (pid + 1)); ++n) {
int m = mode.template index<ThreadsN>(pid, k, n);
void*& p = vec[static_cast<std::size_t>(m)];
std::size_t s = sizes__[static_cast<std::size_t>(m)];
if (p == nullptr) {
p = AllocT::alloc(s);
}
else {
AllocT::free(p, s);
p = nullptr;
}
}
};
}
ipc_ut::sender().wait_for_done();
sw.print_elapsed<1>(DataMin, DataMax, LoopCount, msg.c_str());
}
template <typename AllocT, typename ModeT, int ThreadsN>
struct test_performance {
static void start(char const * message) {
test_performance<AllocT, ModeT, ThreadsN / 2>::start(message);
benchmark_alloc<AllocT, ModeT, ThreadsN>(message);
}
};
template <typename AllocT, typename ModeT>
struct test_performance<AllocT, ModeT, 1> {
static void start(char const * message) {
benchmark_alloc<AllocT, ModeT, 1>(message);
}
};
template <typename AllocT, int ThreadsN>
struct test_performance<AllocT, void, ThreadsN> {
static void start(char const * message) {
test_performance<AllocT, void, ThreadsN / 2>::start(message);
benchmark_alloc<AllocT, ThreadsN>(message);
}
};
template <typename AllocT>
struct test_performance<AllocT, void, 1> {
static void start(char const * message) {
benchmark_alloc<AllocT, 1>(message);
}
};
// class tc_alloc {
// public:
// static void clear() {}
// static void* alloc(std::size_t size) {
// return size ? tc_malloc(size) : nullptr;
// }
// static void free(void* p, std::size_t size) {
// tc_free_sized(p, size);
// }
// };
/*
TEST(Memory, static_alloc) {
test_performance<ipc::mem::static_alloc, void , ThreadMax>::start("alloc-free");
test_performance<ipc::mem::static_alloc, alloc_FIFO , ThreadMax>::start("alloc-FIFO");
test_performance<ipc::mem::static_alloc, alloc_LIFO , ThreadMax>::start("alloc-LIFO");
test_performance<ipc::mem::static_alloc, alloc_Random, ThreadMax>::start("alloc-Rand");
}
TEST(Memory, pool_alloc) {
test_performance<ipc::mem::async_pool_alloc, void , ThreadMax>::start("alloc-free");
test_performance<ipc::mem::async_pool_alloc, alloc_FIFO , ThreadMax>::start("alloc-FIFO");
test_performance<ipc::mem::async_pool_alloc, alloc_LIFO , ThreadMax>::start("alloc-LIFO");
test_performance<ipc::mem::async_pool_alloc, alloc_Random, ThreadMax>::start("alloc-Rand");
}
*/
// TEST(Memory, tc_alloc) {
// test_performance<tc_alloc, void , ThreadMax>::start();
// test_performance<tc_alloc, alloc_FIFO , ThreadMax>::start();
// test_performance<tc_alloc, alloc_LIFO , ThreadMax>::start();
// test_performance<tc_alloc, alloc_Random, ThreadMax>::start();
// }
} // 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));

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,10 +36,8 @@ TEST(PThread, Robust) {
pthread_mutex_unlock(&mutex);
pthread_mutex_destroy(&mutex);
}
#elif defined(IPC_OS_WINDOWS_)
#if defined(__MINGW32__)
#include <windows.h>
#else
#elif defined(LIBIPC_OS_WIN)
#include <Windows.h>
#include <Windows.h>
#endif
#include <tchar.h>

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

View File

@ -0,0 +1,57 @@
#include "../archive/test.h"
#include "libipc/imp/byte.h"
#include "libipc/imp/span.h"
#include "libipc/imp/detect_plat.h"
TEST(byte, construct) {
{
LIBIPC_UNUSED ipc::byte b;
SUCCEED();
}
{
ipc::byte b{};
EXPECT_EQ(int(b), 0);
}
{
ipc::byte b{123};
EXPECT_EQ(int(b), 123);
}
{
ipc::byte b{65535};
EXPECT_EQ(int(b), 255);
EXPECT_EQ(std::int8_t(b), -1);
}
{
ipc::byte b{65536};
EXPECT_EQ(int(b), 0);
}
}
TEST(byte, compare) {
{
ipc::byte b1{}, b2{};
EXPECT_EQ(b1, b2);
}
{
ipc::byte b1{}, b2(321);
EXPECT_NE(b1, b2);
}
}
TEST(byte, byte_cast) {
int a = 654321;
int *pa = &a;
// int * => byte *
ipc::byte *pb = ipc::byte_cast(pa);
EXPECT_EQ((std::size_t)pb, (std::size_t)pa);
// byte * => int32_t *
std::int32_t *pc = ipc::byte_cast<std::int32_t>(pb);
EXPECT_EQ(*pc, a);
// byte alignment check
EXPECT_EQ(ipc::byte_cast<int>(pb + 1), nullptr);
}

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