Repository: Alairion/not-enough-standards Branch: master Commit: 9013a68ab88d Files: 20 Total size: 189.7 KB Directory structure: gitextract_vd_i4h3q/ ├── .clang-format ├── .github/ │ └── workflows/ │ └── common.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake/ │ └── NotEnoughStandards.cmake.in ├── include/ │ └── nes/ │ ├── named_mutex.hpp │ ├── named_semaphore.hpp │ ├── pipe.hpp │ ├── process.hpp │ ├── semaphore.hpp │ ├── shared_library.hpp │ ├── shared_memory.hpp │ └── thread_pool.hpp └── tests/ ├── common.hpp ├── library.cpp ├── process.cpp ├── process_other.cpp └── thread_pool_test.cpp ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ Language: Cpp AccessModifierOffset: -4 AlignAfterOpenBracket: DontAlign AlignArrayOfStructures: Left AlignConsecutiveAssignments: None AlignConsecutiveBitFields: None AlignConsecutiveDeclarations: None AlignConsecutiveMacros: None AlignEscapedNewlines: Left AlignOperands: Align AlignTrailingComments: true AllowAllArgumentsOnNextLine: false AllowAllParametersOfDeclarationOnNextLine: false AllowShortEnumsOnASingleLine: false AllowShortBlocksOnASingleLine: Never AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortLambdasOnASingleLine: None AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: MultiLine BinPackArguments: true BinPackParameters: true BraceWrapping: AfterCaseLabel: true AfterClass: true AfterControlStatement: Always AfterEnum: true AfterFunction: true AfterNamespace: true AfterObjCDeclaration: true AfterStruct: true AfterUnion: true AfterExternBlock: true BeforeCatch: true BeforeElse: true BeforeLambdaBody: false BeforeWhile: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: None BreakBeforeConceptDeclarations: Always BreakBeforeBraces: Allman BreakBeforeInheritanceComma: false BreakInheritanceList: AfterColon BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeComma BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 0 CommentPragmas: '^ IWYU pragma: ' QualifierAlignment: Leave CompactNamespaces: false ConstructorInitializerIndentWidth: 0 ContinuationIndentWidth: 0 Cpp11BracedListStyle: true DeriveLineEnding: true DerivePointerAlignment: false DisableFormat: false EmptyLineAfterAccessModifier: Never EmptyLineBeforeAccessModifier: LogicalBlock ExperimentalAutoDetectBinPacking: false PackConstructorInitializers: NextLine BasedOnStyle: '' ConstructorInitializerAllOnOneLineOrOnePerLine: false AllowAllConstructorInitializersOnNextLine: true FixNamespaceComments: false IncludeBlocks: Preserve IndentAccessModifiers: false IndentCaseLabels: false IndentCaseBlocks: false IndentGotoLabels: true IndentPPDirectives: BeforeHash IndentExternBlock: AfterExternBlock IndentRequiresClause: true IndentWidth: 4 IndentWrappedFunctionNames: false InsertBraces: true InsertTrailingCommas: None JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: true LambdaBodyIndentation: OuterScope MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 2 ObjCBreakBeforeNestedBlockParam: true ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakOpenParenthesis: 0 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 PenaltyIndentedWhitespace: 0 PointerAlignment: Left PPIndentWidth: -1 ReferenceAlignment: Pointer ReflowComments: true RemoveBracesLLVM: false RequiresClausePosition: OwnLine SeparateDefinitionBlocks: Leave ShortNamespaceLines: 1 SortIncludes: Never SortUsingDeclarations: false SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: false SpaceBeforeAssignmentOperators: true SpaceBeforeCaseColon: false SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: false SpaceBeforeInheritanceColon: true SpaceBeforeParens: Never SpaceBeforeParensOptions: AfterControlStatements: true AfterForeachMacros: true AfterFunctionDefinitionName: false AfterFunctionDeclarationName: false AfterIfMacros: true AfterOverloadedOperator: false AfterRequiresInClause: false AfterRequiresInExpression: false BeforeNonEmptyParentheses: false SpaceAroundPointerQualifiers: Default SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyBlock: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: Never SpacesInConditionalStatement: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInLineCommentPrefix: Minimum: 1 Maximum: -1 SpacesInParentheses: false SpacesInSquareBrackets: false SpaceBeforeSquareBrackets: false BitFieldColonSpacing: Both Standard: Latest TabWidth: 4 UseCRLF: false UseTab: Never ================================================ FILE: .github/workflows/common.yml ================================================ name: CI on: push: branches: [ "master" ] pull_request: branches: [ "master" ] env: BUILD_TYPE: Debug jobs: build-linux: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE:STRING=${{env.BUILD_TYPE}} -DBUILD_TESTING:BOOL=ON - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - name: Test working-directory: ${{github.workspace}}/build${{ matrix.subdir }} run: ctest -VV -C ${{env.BUILD_TYPE}} build-windows: runs-on: windows-latest steps: - uses: actions/checkout@v3 - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_CONFIGURATION_TYPES:STRING=${{env.BUILD_TYPE}} -DBUILD_TESTING:BOOL=ON - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - name: Test working-directory: ${{github.workspace}}/build run: | Add-Content $env:GITHUB_PATH "${{github.workspace}}\build\Debug" ctest -VV -C ${{env.BUILD_TYPE}} ================================================ FILE: .gitignore ================================================ # Prerequisites *.d # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod *.smod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app # IDEs .vs *.user out bin build ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.23) # required for file sets project(NotEnoughStandards VERSION 1.1.0) if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}) option(BUILD_TESTING "Build Not Enough Standards' tests" OFF) else() # don't use caller cache set(BUILD_TESTING OFF) endif() add_library(NotEnoughStandards INTERFACE) target_compile_features(NotEnoughStandards INTERFACE cxx_std_20) target_sources(NotEnoughStandards INTERFACE FILE_SET HEADERS BASE_DIRS include FILES include/nes/shared_library.hpp include/nes/shared_memory.hpp include/nes/named_mutex.hpp include/nes/semaphore.hpp include/nes/named_semaphore.hpp include/nes/pipe.hpp include/nes/process.hpp include/nes/thread_pool.hpp ) if(UNIX) target_link_libraries(NotEnoughStandards INTERFACE dl) if(NOT ${CMAKE_SYSTEM_NAME} STREQUAL ANDROID) target_link_libraries(NotEnoughStandards INTERFACE pthread rt) endif() endif() if(BUILD_TESTING OR NOT_ENOUGH_STANDARDS_BUILD_TESTS) include(CTest) get_property(multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) add_executable(NotEnoughStandardsTest tests/common.hpp tests/process.cpp) target_link_libraries(NotEnoughStandardsTest PRIVATE NotEnoughStandards) add_executable(NotEnoughStandardsTestOther tests/common.hpp tests/process_other.cpp) target_link_libraries(NotEnoughStandardsTestOther PRIVATE NotEnoughStandards) add_library(NotEnoughStandardsTestLib SHARED tests/common.hpp tests/library.cpp) set_target_properties(NotEnoughStandardsTestLib PROPERTIES PREFIX "") target_link_libraries(NotEnoughStandardsTestLib PRIVATE NotEnoughStandards) add_test(NAME NotEnoughStandardsTest COMMAND NotEnoughStandardsTest) if(multi_config) set_tests_properties(NotEnoughStandardsTest PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/$) endif() add_executable(NotEnoughStandardsThreadPoolTest tests/common.hpp tests/thread_pool_test.cpp) target_link_libraries(NotEnoughStandardsThreadPoolTest PRIVATE NotEnoughStandards) add_test(NAME NotEnoughStandardsThreadPoolTest COMMAND NotEnoughStandardsThreadPoolTest) endif() include(CMakePackageConfigHelpers) configure_package_config_file( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/NotEnoughStandards.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/NotEnoughStandardsConfig.cmake INSTALL_DESTINATION lib/cmake/NotEnoughStandards ) write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/NotEnoughStandardsConfigVersion.cmake VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion ) install(TARGETS NotEnoughStandards EXPORT NotEnoughStandardsTargets FILE_SET HEADERS DESTINATION include ) install(EXPORT NotEnoughStandardsTargets DESTINATION lib/cmake/NotEnoughStandards NAMESPACE NotEnoughStandards:: ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/NotEnoughStandardsConfigVersion.cmake ${CMAKE_CURRENT_BINARY_DIR}/NotEnoughStandardsConfig.cmake DESTINATION lib/cmake/NotEnoughStandards ) ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 Alexy Pellegrini Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Not Enough Standards Not Enough Standards is a modern header-only C++17 and C++20 library that provides platform-independent utilities. The goal of this library is to extend the standard library with recurent features, such as process management, shared library loading or thread pools. To reach that goal the library is written in a very standard compliant way, from the coding-style to the naming convention. ## Features Not Enough Standards works on any posix-compliant system and also on Windows. * [Shared library loading](https://github.com/Alairion/not-enough-standards/wiki/shared_library.hpp) * [Process management](https://github.com/Alairion/not-enough-standards/wiki/process.hpp) * Inter-process communication ([pipes](https://github.com/Alairion/not-enough-standards/wiki/pipe.hpp), [shared memory](https://github.com/Alairion/not-enough-standards/wiki/shared_memory.hpp)) * Inter-process synchronization ([named mutexes](https://github.com/Alairion/not-enough-standards/wiki/named_mutex.hpp), [named semaphores](https://github.com/Alairion/not-enough-standards/wiki/names_semaphore.hpp)) * Synchronization primitives ([semaphores](https://github.com/Alairion/not-enough-standards/wiki/semaphore.hpp)) * [Thread pools](https://github.com/Alairion/not-enough-standards/wiki/thread_pool.hpp) Check out the [Wiki](https://github.com/Alairion/not-enough-standards/wiki) for more informations. ## Installation Not Enough Standards requires a C++17 compiler, and a C++20 compiler for thread pools. As any header only library, Not Enough Standards is designed to be directly included in your project, by copying the files you need in your project's directory. You may also use it as a CMake subproject using `add_subdirectory`, or by find package, and use it as any other library: ``` target_link_libraries(xxx PRIVATE NotEnoughStandards::NotEnoughStandards) ``` The files of the library are independent from each others, so if you only need one specific feature, you can use only the header that contains it. Actually the only file with a dependency is `process.hpp` which defines more features if `pipe.hpp` is available. ## Usage Here is a short example using Not Enough Standards: #### main.cpp ```cpp #include #include int main() { //nes::this_process namespace can be used to modify current process or get informations about it. std::cout << "Current process has id " << nes::this_process::get_id() << std::endl; std::cout << "Its current directory is \"" << nes::this_process::working_directory() << "\"" << std::endl; //Create a child process nes::process other{"other_process", {"Hey!", "\\\"12\"\"\\\\", "\\42\\", "It's \"me\"!"}, nes::process_options::grab_stdout}; //Read the entire standard output of the child process. (nes::process_options::grab_stdout must be specified on process creation) std::cout << other.stdout_stream().rdbuf() << std::endl; //As a std::thread, a nes::process must be joined if it is not detached. if(other.joinable()) other.join(); //Once joined, we can check its return code. std::cout << "Other process ended with code: " << other.return_code() << std::endl; } ``` #### other.cpp ```cpp #include #include int main(int argc, char** argv) { //Output some informations about this process std::cout << "Hello world! I'm Other!\n"; std::cout << "You gave me " << argc << " arguments:"; for(int i{}; i < argc; ++i) std::cout << "[" << argv[i] << "] "; std::cout << '\n'; std::cout << "My working directory is \"" << nes::this_process::working_directory() << "\"" << std::endl; } ``` #### Output ``` Current process has id 3612 Its current directory is "/..." Hello world! I'm Other! You gave me 5 arguments:[not_enough_standards_other.exe] [Hey!] [\"12""\\\] [\42\] [It's "me"!] My working directory is "/..." Other process ended with code: 0 ``` ## License Not Enough Standards use the [MIT license](https://opensource.org/licenses/MIT). ================================================ FILE: cmake/NotEnoughStandards.cmake.in ================================================ @PACKAGE_INIT@ include("${CMAKE_CURRENT_LIST_DIR}/NotEnoughStandardsTargets.cmake") check_required_components("@PROJECT_NAME@") ================================================ FILE: include/nes/named_mutex.hpp ================================================ /////////////////////////////////////////////////////////// /// Copyright 2019 Alexy Pellegrini /// /// Permission is hereby granted, free of charge, /// to any person obtaining a copy of this software /// and associated documentation files (the "Software"), /// to deal in the Software without restriction, /// including without limitation the rights to use, /// copy, modify, merge, publish, distribute, sublicense, /// and/or sell copies of the Software, and to permit /// persons to whom the Software is furnished to do so, /// subject to the following conditions: /// /// The above copyright notice and this permission notice /// shall be included in all copies or substantial portions /// of the Software. /// /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF /// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED /// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A /// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT /// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR /// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN /// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE /// OR OTHER DEALINGS IN THE SOFTWARE. /////////////////////////////////////////////////////////// #ifndef NOT_ENOUGH_STANDARDS_NAMED_MUTEX #define NOT_ENOUGH_STANDARDS_NAMED_MUTEX #if defined(_WIN32) #define NES_WIN32_NAMED_MUTEX #ifndef NOMINMAX #define NOMINMAX #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #elif defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) #define NES_POSIX_NAMED_MUTEX #include #include #include #include #include #include #else #error "Not enough standards does not support this environment." #endif #ifdef NES_INLINE_NAMESPACE #define NES_INLINE_NAMESPACE_BEGIN \ inline namespace NES_INLINE_NAMESPACE \ { #define NES_INLINE_NAMESPACE_END } #else #define NES_INLINE_NAMESPACE_BEGIN #define NES_INLINE_NAMESPACE_END #endif #include #include #include #include #include #if defined(NES_WIN32_NAMED_MUTEX) namespace nes { NES_INLINE_NAMESPACE_BEGIN inline constexpr const char named_mutex_root[] = "Local\\"; namespace impl { struct named_mutex_base { HANDLE create_or_open(const std::string& name) { const auto native_name{to_wide(named_mutex_root + name)}; HANDLE handle{CreateMutexW(nullptr, FALSE, std::data(native_name))}; if(!handle) { if(GetLastError() == ERROR_ACCESS_DENIED) { handle = OpenMutexW(SYNCHRONIZE, FALSE, std::data(native_name)); if(!handle) { throw std::runtime_error{"Failed to open named mutex. " + get_error_message()}; } } else { throw std::runtime_error{"Failed to create named mutex. " + get_error_message()}; } } return handle; } std::wstring to_wide(const std::string& path) { assert(std::size(path) < 0x7FFFFFFFu && "Wrong path."); if(std::empty(path)) { return {}; } std::wstring out_path{}; out_path.resize(static_cast(MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, std::data(path), static_cast(std::size(path)), nullptr, 0))); if(!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, std::data(path), static_cast(std::size(path)), std::data(out_path), static_cast(std::size(out_path)))) { throw std::runtime_error{"Failed to convert the path to wide."}; } return out_path; } std::string get_error_message() const { return "#" + std::to_string(GetLastError()); } }; } class named_mutex : impl::named_mutex_base { public: using native_handle_type = HANDLE; public: explicit named_mutex(const std::string& name) : m_handle{create_or_open(name)} { } ~named_mutex() { CloseHandle(m_handle); } named_mutex(const named_mutex&) = delete; named_mutex& operator=(const named_mutex&) = delete; named_mutex(named_mutex&&) noexcept = delete; named_mutex& operator=(named_mutex&&) noexcept = delete; void lock() { if(WaitForSingleObject(m_handle, INFINITE) == WAIT_FAILED) { throw std::runtime_error{"Failed to lock mutex. " + get_error_message()}; } } bool try_lock() { return WaitForSingleObject(m_handle, 0) == WAIT_OBJECT_0; } void unlock() { ReleaseMutex(m_handle); } native_handle_type native_handle() const noexcept { return m_handle; } private: native_handle_type m_handle{}; }; class timed_named_mutex : impl::named_mutex_base { public: using native_handle_type = HANDLE; public: explicit timed_named_mutex(const std::string& name) : m_handle{create_or_open(name)} { } ~timed_named_mutex() { CloseHandle(m_handle); } timed_named_mutex(const timed_named_mutex&) = delete; timed_named_mutex& operator=(const timed_named_mutex&) = delete; timed_named_mutex(timed_named_mutex&&) noexcept = delete; timed_named_mutex& operator=(timed_named_mutex&&) noexcept = delete; void lock() { if(WaitForSingleObject(m_handle, INFINITE) == WAIT_FAILED) { throw std::runtime_error{"Failed to lock mutex. " + get_error_message()}; } } bool try_lock() { return WaitForSingleObject(m_handle, 0) == WAIT_OBJECT_0; } template bool try_lock_for(const std::chrono::duration& timeout) { return WaitForSingleObject(m_handle, static_cast(std::chrono::duration_cast(timeout).count())) == WAIT_OBJECT_0; } template bool try_lock_until(const std::chrono::time_point& time_point) { const auto current_time{Clock::now()}; if(time_point < current_time) { return try_lock(); } return try_lock_for(time_point - current_time); } void unlock() { ReleaseMutex(m_handle); } native_handle_type native_handle() const noexcept { return m_handle; } private: native_handle_type m_handle{}; }; class recursive_named_mutex : public named_mutex { }; class recursive_timed_named_mutex : public timed_named_mutex { }; NES_INLINE_NAMESPACE_END } #elif defined(NES_POSIX_NAMED_MUTEX) namespace nes { NES_INLINE_NAMESPACE_BEGIN inline constexpr const char named_mutex_root[] = "/"; namespace impl { struct mutex_data { std::uint64_t opened{}; pthread_mutex_t mutex{}; }; struct mutex_base { int memory{-1}; mutex_data* data{}; }; inline mutex_base create_or_open_mutex(const std::string& name, bool recursive) { const auto native_name{named_mutex_root + name}; int shm_handle{shm_open(std::data(native_name), O_RDWR | O_CREAT, 0660)}; if(shm_handle == -1) { throw std::runtime_error{"Failed to allocate space for named mutex. " + std::string{strerror(errno)}}; } if(ftruncate(shm_handle, sizeof(mutex_data)) == -1) { close(shm_handle); throw std::runtime_error{"Failed to truncate shared memory for named mutex. " + std::string{strerror(errno)}}; } auto* ptr{reinterpret_cast(mmap(nullptr, sizeof(mutex_data), PROT_READ | PROT_WRITE, MAP_SHARED, shm_handle, 0))}; if(ptr == MAP_FAILED) { close(shm_handle); throw std::runtime_error{"Failed to map shared memory for named mutex. " + std::string{strerror(errno)}}; } if(!ptr->opened) { pthread_mutexattr_t attr{}; pthread_mutexattr_init(&attr); auto clean_and_throw = [ptr, shm_handle, &attr](const std::string& error_str, int error) { munmap(ptr, sizeof(mutex_data)); close(shm_handle); pthread_mutexattr_destroy(&attr); throw std::runtime_error{error_str + std::string{strerror(error)}}; }; if(auto error = pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); error != 0) { clean_and_throw("Failed to set process shared attribute of mutex. ", error); } if(auto error = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST); error != 0) { clean_and_throw("Failed to set robust attribute of mutex. ", error); } if(recursive) { if(auto error = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); error != 0) { clean_and_throw("Failed to set recursive attribute of mutex. ", error); } } if(auto error = pthread_mutex_init(&ptr->mutex, &attr); error != 0) { clean_and_throw("Failed to init mutex. ", error); } pthread_mutexattr_destroy(&attr); ptr->opened = 1; } return mutex_base{shm_handle, ptr}; } inline void close_mutex(mutex_base& mutex) { if(mutex.data) { munmap(std::exchange(mutex.data, nullptr), sizeof(mutex_data)); } if(mutex.memory != -1) { close(std::exchange(mutex.memory, -1)); } } inline void lock_mutex(mutex_base& mutex) { auto error{pthread_mutex_lock(&mutex.data->mutex)}; if(error == EOWNERDEAD) { pthread_mutex_consistent(&mutex.data->mutex); } else if(error != 0) { throw std::runtime_error{"Failed to lock mutex. " + std::string{strerror(error)}}; } } inline bool try_lock_mutex(mutex_base& mutex) { auto error{pthread_mutex_trylock(&mutex.data->mutex)}; if(error == EOWNERDEAD) { pthread_mutex_consistent(&mutex.data->mutex); return true; } return !error; } inline bool try_lock_mutex_until(mutex_base& mutex, const timespec& time) { auto error{pthread_mutex_timedlock(&mutex.data->mutex, &time)}; if(error == EOWNERDEAD) { pthread_mutex_consistent(&mutex.data->mutex); return true; } return !error; } } class named_mutex { public: using native_handle_type = pthread_mutex_t*; public: explicit named_mutex(const std::string& name) : m_handle{impl::create_or_open_mutex(name, false)} { } ~named_mutex() { impl::close_mutex(m_handle); } named_mutex(const named_mutex&) = delete; named_mutex& operator=(const named_mutex&) = delete; named_mutex(named_mutex&&) noexcept = delete; named_mutex& operator=(named_mutex&&) noexcept = delete; void lock() { impl::lock_mutex(m_handle); } bool try_lock() { return impl::try_lock_mutex(m_handle); } void unlock() { pthread_mutex_unlock(&m_handle.data->mutex); } native_handle_type native_handle() const noexcept { return &m_handle.data->mutex; } private: impl::mutex_base m_handle{}; }; class timed_named_mutex { public: using native_handle_type = pthread_mutex_t*; public: explicit timed_named_mutex(const std::string& name) : m_handle{impl::create_or_open_mutex(name, false)} { } ~timed_named_mutex() { impl::close_mutex(m_handle); } timed_named_mutex(const timed_named_mutex&) = delete; timed_named_mutex& operator=(const timed_named_mutex&) = delete; timed_named_mutex(timed_named_mutex&&) noexcept = delete; timed_named_mutex& operator=(timed_named_mutex&&) noexcept = delete; void lock() { impl::lock_mutex(m_handle); } bool try_lock() { return impl::try_lock_mutex(m_handle); } template bool try_lock_for(const std::chrono::duration& timeout) { return try_lock_until(std::chrono::system_clock::now() + timeout); } template bool try_lock_until(const std::chrono::time_point& time_point) { const auto seconds{std::chrono::time_point_cast(time_point)}; const auto nanoseconds{std::chrono::duration_cast(time_point - seconds)}; timespec time{}; time.tv_sec = static_cast(seconds.time_since_epoch().count()); time.tv_nsec = static_cast(nanoseconds.count()); return impl::try_lock_mutex_until(m_handle, time); } void unlock() { pthread_mutex_unlock(&m_handle.data->mutex); } native_handle_type native_handle() const noexcept { return &m_handle.data->mutex; } private: impl::mutex_base m_handle{}; }; class recursive_named_mutex { public: using native_handle_type = pthread_mutex_t*; public: explicit recursive_named_mutex(const std::string& name) : m_handle{impl::create_or_open_mutex(name, true)} { } ~recursive_named_mutex() { impl::close_mutex(m_handle); } recursive_named_mutex(const recursive_named_mutex&) = delete; recursive_named_mutex& operator=(const recursive_named_mutex&) = delete; recursive_named_mutex(recursive_named_mutex&&) noexcept = delete; recursive_named_mutex& operator=(recursive_named_mutex&&) noexcept = delete; void lock() { impl::lock_mutex(m_handle); } bool try_lock() { return impl::try_lock_mutex(m_handle); } void unlock() { pthread_mutex_unlock(&m_handle.data->mutex); } native_handle_type native_handle() const noexcept { return &m_handle.data->mutex; } private: impl::mutex_base m_handle{}; }; class recursive_timed_named_mutex { public: using native_handle_type = pthread_mutex_t*; public: explicit recursive_timed_named_mutex(const std::string& name) : m_handle{impl::create_or_open_mutex(name, true)} { } ~recursive_timed_named_mutex() { impl::close_mutex(m_handle); } recursive_timed_named_mutex(const recursive_timed_named_mutex&) = delete; recursive_timed_named_mutex& operator=(const recursive_timed_named_mutex&) = delete; recursive_timed_named_mutex(recursive_timed_named_mutex&&) noexcept = delete; recursive_timed_named_mutex& operator=(recursive_timed_named_mutex&&) noexcept = delete; void lock() { impl::lock_mutex(m_handle); } bool try_lock() { return impl::try_lock_mutex(m_handle); } template bool try_lock_for(const std::chrono::duration& timeout) { return try_lock_until(std::chrono::system_clock::now() + timeout); } template bool try_lock_until(const std::chrono::time_point& time_point) { const auto seconds{std::chrono::time_point_cast(time_point)}; const auto nanoseconds{std::chrono::duration_cast(time_point - seconds)}; timespec time{}; time.tv_sec = static_cast(seconds.time_since_epoch().count()); time.tv_nsec = static_cast(nanoseconds.count()); return impl::try_lock_mutex_until(m_handle, time); } void unlock() { pthread_mutex_unlock(&m_handle.data->mutex); } native_handle_type native_handle() const noexcept { return &m_handle.data->mutex; } private: impl::mutex_base m_handle{}; }; NES_INLINE_NAMESPACE_END } #endif #endif ================================================ FILE: include/nes/named_semaphore.hpp ================================================ /////////////////////////////////////////////////////////// /// Copyright 2019 Alexy Pellegrini /// /// Permission is hereby granted, free of charge, /// to any person obtaining a copy of this software /// and associated documentation files (the "Software"), /// to deal in the Software without restriction, /// including without limitation the rights to use, /// copy, modify, merge, publish, distribute, sublicense, /// and/or sell copies of the Software, and to permit /// persons to whom the Software is furnished to do so, /// subject to the following conditions: /// /// The above copyright notice and this permission notice /// shall be included in all copies or substantial portions /// of the Software. /// /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF /// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED /// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A /// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT /// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR /// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN /// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE /// OR OTHER DEALINGS IN THE SOFTWARE. /////////////////////////////////////////////////////////// #ifndef NOT_ENOUGH_STANDARDS_NAMED_SEMAPHORE #define NOT_ENOUGH_STANDARDS_NAMED_SEMAPHORE #if defined(_WIN32) #define NES_WIN32_NAMED_SEMAPHORE #ifndef NOMINMAX #define NOMINMAX #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #elif defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) #define NES_POSIX_NAMED_SEMAPHORE #include #include #include #include #else #error "Not enough standards does not support this environment." #endif #ifdef NES_INLINE_NAMESPACE #define NES_INLINE_NAMESPACE_BEGIN \ inline namespace NES_INLINE_NAMESPACE \ { #define NES_INLINE_NAMESPACE_END } #else #define NES_INLINE_NAMESPACE_BEGIN #define NES_INLINE_NAMESPACE_END #endif #include #include #include #include #include #include #if defined(NES_WIN32_NAMED_SEMAPHORE) namespace nes { NES_INLINE_NAMESPACE_BEGIN inline constexpr const char named_semaphore_root[] = "Local\\"; class named_semaphore { public: using native_handle_type = HANDLE; public: explicit named_semaphore(const std::string& name, std::size_t initial_count = 0) { const auto native_name{to_wide(named_semaphore_root + name)}; m_handle = CreateSemaphoreW(nullptr, static_cast(initial_count), std::numeric_limits::max(), std::data(native_name)); if(!m_handle) { if(GetLastError() == ERROR_ACCESS_DENIED) { m_handle = OpenSemaphoreW(SYNCHRONIZE, FALSE, std::data(native_name)); if(!m_handle) { throw std::runtime_error{"Failed to open semaphore. " + get_error_message()}; } } else { throw std::runtime_error{"Failed to create semaphore. " + get_error_message()}; } } } ~named_semaphore() { CloseHandle(m_handle); } named_semaphore(const named_semaphore&) = delete; named_semaphore& operator=(const named_semaphore&) = delete; named_semaphore(named_semaphore&& other) noexcept = delete; named_semaphore& operator=(named_semaphore&& other) noexcept = delete; void acquire() { if(WaitForSingleObject(m_handle, INFINITE)) { throw std::runtime_error{"Failed to decrement semaphore count. " + get_error_message()}; } } bool try_acquire() { return WaitForSingleObject(m_handle, 0) == WAIT_OBJECT_0; } void release() { if(!ReleaseSemaphore(m_handle, 1, nullptr)) { throw std::runtime_error{"Failed to increment semaphore count. " + get_error_message()}; } } native_handle_type native_handle() const noexcept { return m_handle; } private: std::wstring to_wide(const std::string& path) { assert(std::size(path) < 0x7FFFFFFFu && "Wrong path."); if(std::empty(path)) { return {}; } std::wstring out_path{}; out_path.resize(static_cast(MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, std::data(path), static_cast(std::size(path)), nullptr, 0))); if(!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, std::data(path), static_cast(std::size(path)), std::data(out_path), static_cast(std::size(out_path)))) { throw std::runtime_error{"Failed to convert the path to wide."}; } return out_path; } std::string get_error_message() const { return "#" + std::to_string(GetLastError()); } private: native_handle_type m_handle{}; }; class timed_named_semaphore { public: using native_handle_type = HANDLE; public: explicit timed_named_semaphore(const std::string& name, std::size_t initial_count = 0) { const auto native_name{to_wide(named_semaphore_root + name)}; m_handle = CreateSemaphoreW(nullptr, static_cast(initial_count), std::numeric_limits::max(), std::data(native_name)); if(!m_handle) { if(GetLastError() == ERROR_ACCESS_DENIED) { m_handle = OpenSemaphoreW(SYNCHRONIZE, FALSE, std::data(native_name)); if(!m_handle) { throw std::runtime_error{"Failed to open semaphore. " + get_error_message()}; } } else { throw std::runtime_error{"Failed to create semaphore. " + get_error_message()}; } } } ~timed_named_semaphore() { CloseHandle(m_handle); } timed_named_semaphore(const timed_named_semaphore&) = delete; timed_named_semaphore& operator=(const timed_named_semaphore&) = delete; timed_named_semaphore(timed_named_semaphore&& other) noexcept = delete; timed_named_semaphore& operator=(timed_named_semaphore&& other) noexcept = delete; void acquire() { if(WaitForSingleObject(m_handle, INFINITE)) { throw std::runtime_error{"Failed to decrement semaphore count. " + get_error_message()}; } } bool try_acquire() { return WaitForSingleObject(m_handle, 0) == WAIT_OBJECT_0; } template bool try_acquire_for(const std::chrono::duration& timeout) { return WaitForSingleObject(m_handle, std::chrono::duration_cast(timeout).count()) == WAIT_OBJECT_0; } template bool try_acquire_until(const std::chrono::time_point& time_point) { const auto current_time{Clock::now()}; if(time_point < current_time) { return try_acquire(); } return try_acquire_for(time_point - current_time); } void release() { if(!ReleaseSemaphore(m_handle, 1, nullptr)) { throw std::runtime_error{"Failed to increment semaphore count. " + get_error_message()}; } } native_handle_type native_handle() const noexcept { return m_handle; } private: std::wstring to_wide(const std::string& path) { assert(std::size(path) < 0x7FFFFFFFu && "Wrong path."); if(std::empty(path)) { return {}; } std::wstring out_path{}; out_path.resize(static_cast(MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, std::data(path), static_cast(std::size(path)), nullptr, 0))); if(!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, std::data(path), static_cast(std::size(path)), std::data(out_path), static_cast(std::size(out_path)))) { throw std::runtime_error{"Failed to convert the path to wide."}; } return out_path; } std::string get_error_message() const { return "#" + std::to_string(GetLastError()); } private: native_handle_type m_handle{}; }; NES_INLINE_NAMESPACE_END } #elif defined(NES_POSIX_NAMED_SEMAPHORE) namespace nes { NES_INLINE_NAMESPACE_BEGIN inline constexpr const char named_semaphore_root[] = "/"; class named_semaphore { public: using native_handle_type = sem_t*; public: explicit named_semaphore(const std::string& name, std::size_t initial_count = 0) { const auto native_name{named_semaphore_root + name}; m_handle = sem_open(std::data(native_name), O_CREAT, 0660, initial_count); if(m_handle == SEM_FAILED) { throw std::runtime_error{"Failed to create semaphore. " + std::string{strerror(errno)}}; } } ~named_semaphore() { sem_close(m_handle); } named_semaphore(const named_semaphore&) = delete; named_semaphore& operator=(const named_semaphore&) = delete; named_semaphore(named_semaphore&& other) noexcept = delete; named_semaphore& operator=(named_semaphore&& other) noexcept = delete; void acquire() { if(sem_wait(m_handle) == -1) { throw std::runtime_error{"Failed to decrement semaphore count. " + std::string{strerror(errno)}}; } } bool try_acquire() { return !sem_trywait(m_handle); } void release() { if(sem_post(m_handle) == -1) { throw std::runtime_error{"Failed to increment semaphore count. " + std::string{strerror(errno)}}; } } native_handle_type native_handle() const noexcept { return m_handle; } private: native_handle_type m_handle{}; }; class timed_named_semaphore { public: using native_handle_type = sem_t*; public: explicit timed_named_semaphore(const std::string& name, std::size_t initial_count = 0) { const auto native_name{named_semaphore_root + name}; m_handle = sem_open(std::data(native_name), O_CREAT, 0660, initial_count); if(m_handle == SEM_FAILED) { throw std::runtime_error{"Failed to create semaphore. " + std::string{strerror(errno)}}; } } ~timed_named_semaphore() { sem_close(m_handle); } timed_named_semaphore(const timed_named_semaphore&) = delete; timed_named_semaphore& operator=(const timed_named_semaphore&) = delete; timed_named_semaphore(timed_named_semaphore&& other) noexcept = delete; timed_named_semaphore& operator=(timed_named_semaphore&& other) noexcept = delete; void acquire() { if(sem_wait(m_handle) == -1) { throw std::runtime_error{"Failed to decrement semaphore count. " + std::string{strerror(errno)}}; } } bool try_acquire() { return !sem_trywait(m_handle); } template bool try_lock_for(const std::chrono::duration& timeout) { return try_lock_until(std::chrono::system_clock::now() + timeout); } template bool try_lock_until(const std::chrono::time_point& time_point) { const auto seconds{std::chrono::time_point_cast(time_point)}; const auto nanoseconds{std::chrono::duration_cast(time_point - seconds)}; timespec time{}; time.tv_sec = static_cast(seconds.time_since_epoch().count()); time.tv_nsec = static_cast(nanoseconds.count()); return !sem_timedwait(m_handle, &time); } void release() { if(sem_post(m_handle) == -1) { throw std::runtime_error{"Failed to increment semaphore count. " + std::string{strerror(errno)}}; } } native_handle_type native_handle() const noexcept { return m_handle; } private: native_handle_type m_handle{}; }; NES_INLINE_NAMESPACE_END } #endif #endif ================================================ FILE: include/nes/pipe.hpp ================================================ /////////////////////////////////////////////////////////// /// Copyright 2019 Alexy Pellegrini /// /// Permission is hereby granted, free of charge, /// to any person obtaining a copy of this software /// and associated documentation files (the "Software"), /// to deal in the Software without restriction, /// including without limitation the rights to use, /// copy, modify, merge, publish, distribute, sublicense, /// and/or sell copies of the Software, and to permit /// persons to whom the Software is furnished to do so, /// subject to the following conditions: /// /// The above copyright notice and this permission notice /// shall be included in all copies or substantial portions /// of the Software. /// /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF /// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED /// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A /// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT /// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR /// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN /// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE /// OR OTHER DEALINGS IN THE SOFTWARE. /////////////////////////////////////////////////////////// #ifndef NOT_ENOUGH_STANDARDS_PIPE #define NOT_ENOUGH_STANDARDS_PIPE #if defined(_WIN32) #define NES_WIN32_PIPE #ifndef NOMINMAX #define NOMINMAX #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #elif defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) #define NES_POSIX_PIPE #include #include #include #include #else #error "Not enough standards does not support this environment." #endif #ifdef NES_INLINE_NAMESPACE #define NES_INLINE_NAMESPACE_BEGIN \ inline namespace NES_INLINE_NAMESPACE \ { #define NES_INLINE_NAMESPACE_END } #else #define NES_INLINE_NAMESPACE_BEGIN #define NES_INLINE_NAMESPACE_END #endif #include #include #include #include #include #include #include #include #if defined(NES_WIN32_PIPE) namespace nes { NES_INLINE_NAMESPACE_BEGIN inline constexpr const char pipe_root[] = "\\\\.\\pipe\\"; template class basic_pipe_istream; template class basic_pipe_ostream; template> std::pair, basic_pipe_ostream> make_anonymous_pipe(); template> class basic_pipe_streambuf : public std::basic_streambuf { private: using parent_type = std::basic_streambuf; public: using char_type = CharT; using traits_type = Traits; using int_type = typename Traits::int_type; using pos_type = typename Traits::pos_type; using off_type = typename Traits::off_type; public: static constexpr std::size_t buf_size{1024}; public: basic_pipe_streambuf() = default; explicit basic_pipe_streambuf(const std::string& name, std::ios_base::openmode mode) { open(name, mode); } virtual ~basic_pipe_streambuf() { close(); } basic_pipe_streambuf(const basic_pipe_streambuf&) = delete; basic_pipe_streambuf& operator=(const basic_pipe_streambuf&) = delete; basic_pipe_streambuf(basic_pipe_streambuf&& other) noexcept : parent_type{other} , m_buffer{std::move(other.m_buffer)} , m_handle{std::exchange(other.m_handle, INVALID_HANDLE_VALUE)} , m_mode{std::exchange(other.m_mode, std::ios_base::openmode{})} { } basic_pipe_streambuf& operator=(basic_pipe_streambuf&& other) noexcept { parent_type::operator=(other); m_buffer = std::move(other.m_buffer); m_handle = std::exchange(other.m_handle, m_handle); m_mode = std::exchange(other.m_mode, m_mode); return *this; } bool open(const std::string& name, std::ios_base::openmode mode) { assert(!((mode & std::ios_base::in) && (mode & std::ios_base::out)) && "nes::basic_pipe_streambuf::open called with mode = std::ios_base::in | std::ios_base::out."); close(); const auto native_name{to_wide(pipe_root + name)}; DWORD native_mode{mode & std::ios_base::in ? GENERIC_READ : GENERIC_WRITE}; HANDLE handle = CreateFileW(std::data(native_name), native_mode, 0, nullptr, OPEN_EXISTING, 0, nullptr); if(handle == INVALID_HANDLE_VALUE) { if(GetLastError() == ERROR_FILE_NOT_FOUND) { native_mode = mode & std::ios_base::in ? PIPE_ACCESS_INBOUND : PIPE_ACCESS_OUTBOUND; handle = CreateNamedPipeW(std::data(native_name), native_mode, PIPE_READMODE_BYTE | PIPE_WAIT, 1, buf_size, buf_size, 0, nullptr); if(handle == INVALID_HANDLE_VALUE) { return false; } if(!ConnectNamedPipe(handle, nullptr)) { CloseHandle(handle); return false; } } } m_buffer.resize(buf_size); parent_type::setp(std::data(m_buffer), std::data(m_buffer) + buf_size); m_handle = handle; m_mode = mode; return true; } bool is_open() const noexcept { return m_handle != INVALID_HANDLE_VALUE; } void close() { if(is_open()) { sync(); m_mode = std::ios_base::openmode{}; CloseHandle(std::exchange(m_handle, INVALID_HANDLE_VALUE)); parent_type::setp(nullptr, nullptr); parent_type::setg(nullptr, nullptr, nullptr); } } private: friend class process; friend std::pair, basic_pipe_ostream> make_anonymous_pipe(); basic_pipe_streambuf(HANDLE handle, std::ios_base::openmode mode) : m_handle{handle} , m_mode{mode} { m_buffer.resize(buf_size); parent_type::setp(std::data(m_buffer), std::data(m_buffer) + buf_size); } protected: virtual int sync() override { if(m_mode & std::ios_base::out) { const std::ptrdiff_t count{parent_type::pptr() - parent_type::pbase()}; DWORD written{}; if(!WriteFile(m_handle, reinterpret_cast(std::data(m_buffer)), static_cast(count) * sizeof(char_type), &written, nullptr)) { return -1; } parent_type::setp(std::data(m_buffer), std::data(m_buffer) + buf_size); } return 0; } virtual int_type overflow(int_type c = traits_type::eof()) override { assert(m_mode & std::ios_base::out && "Write operation on a read only pipe."); if(traits_type::eq_int_type(c, traits_type::eof())) { DWORD written{}; if(!WriteFile(m_handle, reinterpret_cast(std::data(m_buffer)), static_cast(buf_size) * sizeof(char_type), &written, nullptr)) { return traits_type::eof(); } parent_type::setp(std::data(m_buffer), std::data(m_buffer) + buf_size); } else { *parent_type::pptr() = traits_type::to_char_type(c); parent_type::pbump(1); } return traits_type::not_eof(c); } virtual std::streamsize xsputn(const char_type* s, std::streamsize count) override { assert(m_mode & std::ios_base::out && "Write operation on a read only pipe."); sync(); // empty putarea before performing a write DWORD written{}; if(!WriteFile(m_handle, reinterpret_cast(s), static_cast(count) * sizeof(char_type), &written, nullptr)) { return 0; } return static_cast(written); } virtual int_type underflow() override { assert(m_mode & std::ios_base::in && "Read operation on a write only pipe."); if(parent_type::gptr() == parent_type::egptr()) { DWORD read{}; if(!ReadFile(m_handle, reinterpret_cast(std::data(m_buffer)), static_cast(buf_size * sizeof(char_type)), &read, nullptr) || read == 0) { return traits_type::eof(); } parent_type::setg(std::data(m_buffer), std::data(m_buffer), std::data(m_buffer) + (read / sizeof(char_type))); } return traits_type::to_int_type(*parent_type::gptr()); } virtual std::streamsize xsgetn(char_type* s, std::streamsize count) override { assert(m_mode & std::ios_base::in && "Read operation on a write only pipe."); // to support mixing formatted and unformatted input, we have to flush the get buffer const std::ptrdiff_t available = parent_type::egptr() - parent_type::gptr(); if(available > 0) { std::copy_n(parent_type::gptr(), (std::min)(available, count), s); // if we still have buffered input update gptr if(available > count) { parent_type::setg(parent_type::eback(), parent_type::gptr() + count, parent_type::egptr()); return count; } // resets get buffer, this will force an underflow on next formatted input parent_type::setg(nullptr, nullptr, nullptr); if(count == available) { return count; } // perform a read to fulfill the requested count if possible count -= available; s += available; } DWORD read{}; if(!ReadFile(m_handle, reinterpret_cast(s), static_cast(count) * sizeof(char_type), &read, nullptr)) { return 0; } return static_cast(read / sizeof(char_type)) + available; } private: std::wstring to_wide(std::string path) { assert(std::size(path) < 0x7FFFFFFFu && "Wrong path."); if(std::empty(path)) { return {}; } std::transform(std::begin(path), std::end(path), std::begin(path), [](char c) { return c == '/' ? '\\' : c; }); std::wstring out_path{}; out_path.resize(static_cast(MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, std::data(path), static_cast(std::size(path)), nullptr, 0))); if(!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, std::data(path), static_cast(std::size(path)), std::data(out_path), static_cast(std::size(out_path)))) { throw std::runtime_error{"Failed to convert the path to wide."}; } return out_path; } private: std::vector m_buffer{}; HANDLE m_handle{INVALID_HANDLE_VALUE}; std::ios_base::openmode m_mode{}; }; template> class basic_pipe_istream : public std::basic_istream { private: using parent_type = std::basic_istream; public: using char_type = CharT; using traits_type = Traits; using int_type = typename Traits::int_type; using pos_type = typename Traits::pos_type; using off_type = typename Traits::off_type; public: basic_pipe_istream() = default; explicit basic_pipe_istream(const std::string& name, std::ios_base::openmode mode = std::ios_base::in) : parent_type{nullptr} { parent_type::rdbuf(m_buffer.get()); open(name, mode); } virtual ~basic_pipe_istream() = default; basic_pipe_istream(const basic_pipe_istream&) = delete; basic_pipe_istream& operator=(const basic_pipe_istream&) = delete; basic_pipe_istream(basic_pipe_istream&& other) noexcept : parent_type{std::move(other)} { std::swap(m_buffer, other.m_buffer); parent_type::rdbuf(m_buffer.get()); } basic_pipe_istream& operator=(basic_pipe_istream&& other) noexcept { parent_type::operator=(std::move(other)); std::swap(m_buffer, other.m_buffer); parent_type::rdbuf(m_buffer.get()); return *this; } void open(const std::string& name, std::ios_base::openmode mode = std::ios_base::in) { m_buffer->open(name, mode); parent_type::clear(m_buffer->is_open() ? std::ios_base::goodbit : std::ios_base::failbit); } bool is_open() const noexcept { return m_buffer->is_open(); } void close() { m_buffer->close(); } basic_pipe_streambuf* rdbuf() const noexcept { return m_buffer.get(); } private: friend class process; friend std::pair, basic_pipe_ostream> make_anonymous_pipe(); basic_pipe_istream(basic_pipe_streambuf buffer) : parent_type{nullptr} , m_buffer{std::make_unique>(std::move(buffer))} { parent_type::rdbuf(m_buffer.get()); } private: std::unique_ptr> m_buffer{std::make_unique>()}; }; template> class basic_pipe_ostream : public std::basic_ostream { private: using parent_type = std::basic_ostream; public: using char_type = CharT; using traits_type = Traits; using int_type = typename Traits::int_type; using pos_type = typename Traits::pos_type; using off_type = typename Traits::off_type; public: basic_pipe_ostream() = default; explicit basic_pipe_ostream(const std::string& name, std::ios_base::openmode mode = std::ios_base::out) : parent_type{nullptr} { parent_type::rdbuf(m_buffer.get()); open(name, mode); } virtual ~basic_pipe_ostream() = default; basic_pipe_ostream(const basic_pipe_ostream&) = delete; basic_pipe_ostream& operator=(const basic_pipe_ostream&) = delete; basic_pipe_ostream(basic_pipe_ostream&& other) noexcept : parent_type{std::move(other)} { std::swap(m_buffer, other.m_buffer); parent_type::rdbuf(m_buffer.get()); } basic_pipe_ostream& operator=(basic_pipe_ostream&& other) noexcept { parent_type::operator=(std::move(other)); std::swap(m_buffer, other.m_buffer); parent_type::rdbuf(m_buffer.get()); return *this; } void open(const std::string& name, std::ios_base::openmode mode = std::ios_base::out) { m_buffer->open(name, mode); parent_type::clear(m_buffer->is_open() ? std::ios_base::goodbit : std::ios_base::failbit); } bool is_open() const noexcept { return m_buffer->is_open(); } void close() { m_buffer->close(); } basic_pipe_streambuf* rdbuf() const noexcept { return m_buffer.get(); } private: friend class process; friend std::pair, basic_pipe_ostream> make_anonymous_pipe(); basic_pipe_ostream(basic_pipe_streambuf buffer) : parent_type{nullptr} , m_buffer{std::make_unique>(std::move(buffer))} { parent_type::rdbuf(m_buffer.get()); } private: std::unique_ptr> m_buffer{std::make_unique>()}; }; template std::pair, basic_pipe_ostream> make_anonymous_pipe() { HANDLE input{}; HANDLE output{}; if(!CreatePipe(&input, &output, nullptr, 0)) { throw std::runtime_error{"Failed to create pipe"}; } // clang-format off return std::make_pair( basic_pipe_istream{basic_pipe_streambuf{input, std::ios_base::in}}, basic_pipe_ostream{basic_pipe_streambuf{output, std::ios_base::out}}); // clang-format on } using pipe_streambuf = basic_pipe_streambuf; using pipe_istream = basic_pipe_istream; using pipe_ostream = basic_pipe_ostream; NES_INLINE_NAMESPACE_END } #elif defined(NES_POSIX_PIPE) namespace nes { NES_INLINE_NAMESPACE_BEGIN inline constexpr const char pipe_root[] = "/tmp/"; template class basic_pipe_istream; template class basic_pipe_ostream; template> std::pair, basic_pipe_ostream> make_anonymous_pipe(); template> class basic_pipe_streambuf : public std::basic_streambuf { private: using parent_type = std::basic_streambuf; public: using char_type = CharT; using traits_type = Traits; using int_type = typename Traits::int_type; using pos_type = typename Traits::pos_type; using off_type = typename Traits::off_type; public: static constexpr std::size_t buf_size{1024}; public: basic_pipe_streambuf() = default; explicit basic_pipe_streambuf(const std::string& name, std::ios_base::openmode mode) : parent_type{nullptr} { open(name, mode); } virtual ~basic_pipe_streambuf() { close(); } basic_pipe_streambuf(const basic_pipe_streambuf&) = delete; basic_pipe_streambuf& operator=(const basic_pipe_streambuf&) = delete; basic_pipe_streambuf(basic_pipe_streambuf&& other) noexcept : parent_type{std::move(other)} , m_buffer{std::move(other.m_buffer)} , m_handle{std::exchange(other.m_handle, 0)} , m_mode{std::exchange(other.m_mode, std::ios_base::openmode{})} { } basic_pipe_streambuf& operator=(basic_pipe_streambuf&& other) noexcept { parent_type::operator=(std::move(other)); m_buffer = std::move(other.m_buffer); m_handle = std::exchange(other.m_handle, m_handle); m_mode = std::exchange(other.m_mode, m_mode); return *this; } bool open(const std::string& name, std::ios_base::openmode mode) { assert(!((mode & std::ios_base::in) && (mode & std::ios_base::out)) && "nes::basic_pipe_streambuf::open called with mode = std::ios_base::in | std::ios_base::out."); close(); const auto native_name{pipe_root + name}; if(mkfifo(std::data(native_name), 0660) != 0 && errno != EEXIST) { return false; } const int native_mode{mode & std::ios_base::in ? O_RDONLY : O_WRONLY}; int handle = ::open(std::data(native_name), native_mode); if(handle < 0) { return false; } m_buffer.resize(buf_size); parent_type::setp(std::data(m_buffer), std::data(m_buffer) + buf_size); m_handle = handle; m_mode = mode; return true; } bool is_open() const noexcept { return m_handle; } void close() { if(is_open()) { sync(); m_mode = std::ios_base::openmode{}; ::close(std::exchange(m_handle, 0)); parent_type::setp(nullptr, nullptr); parent_type::setg(nullptr, nullptr, nullptr); } } private: friend class process; friend std::pair, basic_pipe_ostream> make_anonymous_pipe(); basic_pipe_streambuf(int handle, std::ios_base::openmode mode) : m_handle{handle} , m_mode{mode} { m_buffer.resize(buf_size); parent_type::setp(std::data(m_buffer), std::data(m_buffer) + buf_size); } protected: virtual int sync() override { if(m_mode & std::ios_base::out) { const std::ptrdiff_t count{parent_type::pptr() - parent_type::pbase()}; if(write(m_handle, reinterpret_cast(std::data(m_buffer)), count * sizeof(char_type)) < 0) { return -1; } parent_type::setp(std::data(m_buffer), std::data(m_buffer) + buf_size); } return 0; } virtual int_type overflow(int_type c = traits_type::eof()) override { assert(m_mode & std::ios_base::out && "Write operation on a read only pipe."); if(traits_type::eq_int_type(c, traits_type::eof())) { if(write(m_handle, reinterpret_cast(std::data(m_buffer)), std::size(m_buffer) * sizeof(char_type))) { return traits_type::eof(); } parent_type::setp(std::data(m_buffer), std::data(m_buffer) + buf_size); } else { *parent_type::pptr() = traits_type::to_char_type(c); parent_type::pbump(1); } return traits_type::not_eof(c); } virtual std::streamsize xsputn(const char_type* s, std::streamsize count) override { assert(m_mode & std::ios_base::out && "Write operation on a read only pipe."); sync(); // empty putarea before performing a write const auto written = write(m_handle, reinterpret_cast(s), count * sizeof(char_type)); if(written < 0) { return 0; } return static_cast(written); } virtual int_type underflow() override { assert(m_mode & std::ios_base::in && "Read operation on a write only pipe."); if(parent_type::gptr() == parent_type::egptr()) { const auto _read = read(m_handle, reinterpret_cast(std::data(m_buffer)), buf_size * sizeof(char_type)); if(_read <= 0) { return traits_type::eof(); } parent_type::setg(std::data(m_buffer), std::data(m_buffer), std::data(m_buffer) + (_read / sizeof(char_type))); } return traits_type::to_int_type(*parent_type::gptr()); } virtual std::streamsize xsgetn(char_type* s, std::streamsize count) override { assert(m_mode & std::ios_base::in && "Read operation on a write only pipe."); // to support mixing formatted and unformatted input, we have to flush the get buffer const std::ptrdiff_t available = parent_type::egptr() - parent_type::gptr(); if(available > 0) { std::copy_n(parent_type::gptr(), (std::min)(available, count), s); // if we still have buffered input update gptr if(available > count) { parent_type::setg(parent_type::eback(), parent_type::gptr() + count, parent_type::egptr()); return count; } // resets get buffer, this will force an underflow on next formatted input parent_type::setg(nullptr, nullptr, nullptr); if(count == available) { return count; } // perform a read to fulfill the requested count if possible count -= available; s += available; } const auto _read = read(m_handle, reinterpret_cast(s), count * sizeof(char_type)); if(_read < 0) { return 0; } return static_cast(_read / sizeof(char_type)) + available; } private: std::vector m_buffer{}; int m_handle{}; std::ios_base::openmode m_mode{}; }; template> class basic_pipe_istream : public std::basic_istream { private: using parent_type = std::basic_istream; public: using char_type = CharT; using traits_type = Traits; using int_type = typename Traits::int_type; using pos_type = typename Traits::pos_type; using off_type = typename Traits::off_type; public: basic_pipe_istream() = default; explicit basic_pipe_istream(const std::string& name, std::ios_base::openmode mode = std::ios_base::in) : parent_type{nullptr} { parent_type::rdbuf(m_buffer.get()); open(name, mode); } virtual ~basic_pipe_istream() = default; basic_pipe_istream(const basic_pipe_istream&) = delete; basic_pipe_istream& operator=(const basic_pipe_istream&) = delete; basic_pipe_istream(basic_pipe_istream&& other) noexcept : parent_type{std::move(other)} { std::swap(m_buffer, other.m_buffer); parent_type::rdbuf(m_buffer.get()); } basic_pipe_istream& operator=(basic_pipe_istream&& other) noexcept { parent_type::operator=(std::move(other)); std::swap(m_buffer, other.m_buffer); parent_type::rdbuf(m_buffer.get()); return *this; } void open(const std::string& name, std::ios_base::openmode mode = std::ios_base::in) { m_buffer->open(name, mode); parent_type::clear(m_buffer->is_open() ? std::ios_base::goodbit : std::ios_base::failbit); } bool is_open() const noexcept { return m_buffer->is_open(); } void close() { m_buffer->close(); } basic_pipe_streambuf* rdbuf() const noexcept { return m_buffer.get(); } private: friend class process; friend std::pair, basic_pipe_ostream> make_anonymous_pipe(); basic_pipe_istream(basic_pipe_streambuf buffer) : parent_type{nullptr} , m_buffer{std::make_unique>(std::move(buffer))} { parent_type::rdbuf(m_buffer.get()); } private: std::unique_ptr> m_buffer{std::make_unique>()}; }; template> class basic_pipe_ostream : public std::basic_ostream { private: using parent_type = std::basic_ostream; public: using char_type = CharT; using traits_type = Traits; using int_type = typename Traits::int_type; using pos_type = typename Traits::pos_type; using off_type = typename Traits::off_type; public: basic_pipe_ostream() = default; explicit basic_pipe_ostream(const std::string& name, std::ios_base::openmode mode = std::ios_base::out) : parent_type{nullptr} { parent_type::rdbuf(m_buffer.get()); open(name, mode); } virtual ~basic_pipe_ostream() = default; basic_pipe_ostream(const basic_pipe_ostream&) = delete; basic_pipe_ostream& operator=(const basic_pipe_ostream&) = delete; basic_pipe_ostream(basic_pipe_ostream&& other) noexcept : parent_type{std::move(other)} { std::swap(m_buffer, other.m_buffer); parent_type::rdbuf(m_buffer.get()); } basic_pipe_ostream& operator=(basic_pipe_ostream&& other) noexcept { parent_type::operator=(std::move(other)); std::swap(m_buffer, other.m_buffer); parent_type::rdbuf(m_buffer.get()); return *this; } void open(const std::string& name, std::ios_base::openmode mode = std::ios_base::out) { m_buffer->open(name, mode); parent_type::clear(m_buffer->is_open() ? std::ios_base::goodbit : std::ios_base::failbit); } bool is_open() const noexcept { return m_buffer->is_open(); } void close() { m_buffer->close(); } basic_pipe_streambuf* rdbuf() const noexcept { return m_buffer.get(); } private: friend class process; friend std::pair, basic_pipe_ostream> make_anonymous_pipe(); basic_pipe_ostream(basic_pipe_streambuf buffer) : parent_type{nullptr} , m_buffer{std::make_unique>(std::move(buffer))} { parent_type::rdbuf(m_buffer.get()); } private: std::unique_ptr> m_buffer{std::make_unique>()}; }; template std::pair, basic_pipe_ostream> make_anonymous_pipe() { int fd[2]; if(pipe(fd)) { throw std::runtime_error{"Failed to create pipe"}; } // clang-format off return std::make_pair( basic_pipe_istream{basic_pipe_streambuf{fd[0], std::ios_base::in}}, basic_pipe_ostream{basic_pipe_streambuf{fd[1], std::ios_base::out}}); // clang-format on } using pipe_streambuf = basic_pipe_streambuf; using pipe_istream = basic_pipe_istream; using pipe_ostream = basic_pipe_ostream; NES_INLINE_NAMESPACE_END } #endif #endif ================================================ FILE: include/nes/process.hpp ================================================ /////////////////////////////////////////////////////////// /// Copyright 2019 Alexy Pellegrini /// /// Permission is hereby granted, free of charge, /// to any person obtaining a copy of this software /// and associated documentation files (the "Software"), /// to deal in the Software without restriction, /// including without limitation the rights to use, /// copy, modify, merge, publish, distribute, sublicense, /// and/or sell copies of the Software, and to permit /// persons to whom the Software is furnished to do so, /// subject to the following conditions: /// /// The above copyright notice and this permission notice /// shall be included in all copies or substantial portions /// of the Software. /// /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF /// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED /// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A /// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT /// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR /// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN /// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE /// OR OTHER DEALINGS IN THE SOFTWARE. /////////////////////////////////////////////////////////// #ifndef NOT_ENOUGH_STANDARDS_PROCESS #define NOT_ENOUGH_STANDARDS_PROCESS #if defined(_WIN32) #define NES_WIN32_PROCESS #ifndef NOMINMAX #define NOMINMAX #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #elif defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) #define NES_POSIX_PROCESS #include #include #include #include #include #else #error "Not enough standards does not support this environment." #endif #if __has_include("pipe.hpp") #define NES_PROCESS_PIPE_EXTENSION #include "pipe.hpp" #endif #ifdef NES_INLINE_NAMESPACE #define NES_INLINE_NAMESPACE_BEGIN \ inline namespace NES_INLINE_NAMESPACE \ { #define NES_INLINE_NAMESPACE_END } #else #define NES_INLINE_NAMESPACE_BEGIN #define NES_INLINE_NAMESPACE_END #endif #include #include #include #include #include #include #include #include #if defined(NES_WIN32_PROCESS) namespace nes { NES_INLINE_NAMESPACE_BEGIN class process; namespace impl { enum class id_t : DWORD { }; constexpr bool operator==(id_t lhs, id_t rhs) noexcept { return static_cast(lhs) == static_cast(rhs); } constexpr bool operator!=(id_t lhs, id_t rhs) noexcept { return static_cast(lhs) != static_cast(rhs); } constexpr bool operator<(id_t lhs, id_t rhs) noexcept { return static_cast(lhs) < static_cast(rhs); } constexpr bool operator<=(id_t lhs, id_t rhs) noexcept { return static_cast(lhs) <= static_cast(rhs); } constexpr bool operator>(id_t lhs, id_t rhs) noexcept { return static_cast(lhs) > static_cast(rhs); } constexpr bool operator>=(id_t lhs, id_t rhs) noexcept { return static_cast(lhs) >= static_cast(rhs); } struct auto_handle { public: constexpr auto_handle() = default; auto_handle(HANDLE h) : m_handle{h} { } ~auto_handle() { if(m_handle != INVALID_HANDLE_VALUE) { CloseHandle(m_handle); } } auto_handle(const auto_handle&) = delete; auto_handle& operator=(const auto_handle&) = delete; auto_handle(auto_handle&& other) noexcept : m_handle{std::exchange(other.m_handle, INVALID_HANDLE_VALUE)} { } auto_handle& operator=(auto_handle&& other) noexcept { m_handle = std::exchange(other.m_handle, m_handle); return *this; } HANDLE release() noexcept { return std::exchange(m_handle, INVALID_HANDLE_VALUE); } operator HANDLE() const noexcept { return m_handle; } HANDLE* operator&() noexcept { return &m_handle; } const HANDLE* operator&() const noexcept { return &m_handle; } operator bool() const noexcept { return m_handle != INVALID_HANDLE_VALUE; } private: HANDLE m_handle{INVALID_HANDLE_VALUE}; }; } enum class process_options : std::uint32_t { none = 0x00, #ifdef NES_PROCESS_PIPE_EXTENSION grab_stdout = 0x10, grab_stderr = 0x20, grab_stdin = 0x40 #endif }; constexpr process_options operator&(process_options left, process_options right) noexcept { return static_cast(static_cast(left) & static_cast(right)); } constexpr process_options& operator&=(process_options& left, process_options right) noexcept { left = left & right; return left; } constexpr process_options operator|(process_options left, process_options right) noexcept { return static_cast(static_cast(left) | static_cast(right)); } constexpr process_options& operator|=(process_options& left, process_options right) noexcept { left = left | right; return left; } constexpr process_options operator^(process_options left, process_options right) noexcept { return static_cast(static_cast(left) ^ static_cast(right)); } constexpr process_options& operator^=(process_options& left, process_options right) noexcept { left = left ^ right; return left; } constexpr process_options operator~(process_options value) noexcept { return static_cast(~static_cast(value)); } class process { public: using native_handle_type = HANDLE; using return_code_type = DWORD; using id = impl::id_t; public: constexpr process() noexcept = default; explicit process(const std::string& path, const std::string& working_directory) : process{path, {}, working_directory, {}} { } explicit process(const std::string& path, process_options options) : process{path, {}, {}, options} { } explicit process(const std::string& path, const std::vector& args, process_options options) : process{path, args, {}, options} { } explicit process(const std::string& path, const std::string& working_directory, process_options options) : process{path, {}, working_directory, options} { } explicit process(const std::string& path, std::vector args = std::vector{}, const std::string& working_directory = std::string{}, process_options options [[maybe_unused]] = process_options{}) { assert(!std::empty(path) && "nes::process::process called with empty path."); SECURITY_ATTRIBUTES security_attributes{}; security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); security_attributes.bInheritHandle = TRUE; security_attributes.lpSecurityDescriptor = nullptr; #ifdef NES_PROCESS_PIPE_EXTENSION impl::auto_handle stdin_rd{}; impl::auto_handle stdout_rd{}; impl::auto_handle stderr_rd{}; impl::auto_handle stdin_wr{}; impl::auto_handle stdout_wr{}; impl::auto_handle stderr_wr{}; if(static_cast(options & process_options::grab_stdin)) { if(!CreatePipe(&stdin_rd, &stdin_wr, &security_attributes, 0) || !SetHandleInformation(stdin_wr, HANDLE_FLAG_INHERIT, 0)) { throw std::runtime_error{"Failed to create stdin pipe. " + get_error_message()}; } } if(static_cast(options & process_options::grab_stdout)) { if(!CreatePipe(&stdout_rd, &stdout_wr, &security_attributes, 0) || !SetHandleInformation(stdout_rd, HANDLE_FLAG_INHERIT, 0)) { throw std::runtime_error{"Failed to create stdout pipe. " + get_error_message()}; } } if(static_cast(options & process_options::grab_stderr)) { if(!CreatePipe(&stderr_rd, &stderr_wr, &security_attributes, 0) || !SetHandleInformation(stderr_rd, HANDLE_FLAG_INHERIT, 0)) { throw std::runtime_error{"Failed to create stderr pipe. " + get_error_message()}; } } #endif args.insert(std::begin(args), path); auto format_arg = [](const std::wstring& arg) -> std::wstring { if(arg.find_first_of(L" \t\n\v\"") == std::wstring::npos) { return arg; } std::wstring out{L"\""}; for(auto it = std::cbegin(arg); it != std::cend(arg); ++it) { if(*it == L'\\') { std::size_t count{1}; while(++it != std::cend(arg) && *it == L'\\') { ++count; } if(it == std::cend(arg)) { out.append(count * 2, L'\\'); break; } else if(*it == L'\"') { out.append(count * 2 + 1, L'\\'); out.push_back(L'\"'); } else { out.append(count, L'\\'); out.push_back(*it); } } else if(*it == L'\"') { out.push_back(L'\\'); out.push_back(*it); } else { out.push_back(*it); } } out.push_back(L'\"'); return out; }; std::wstring args_str{}; for(auto&& arg : args) { args_str += format_arg(to_wide(arg)) + L" "; } const std::wstring native_working_directory{to_wide(working_directory)}; const std::wstring native_path{to_wide(path)}; STARTUPINFOW startup_info{}; startup_info.cb = sizeof(STARTUPINFOW); #ifdef NES_PROCESS_PIPE_EXTENSION startup_info.hStdInput = stdin_rd; startup_info.hStdOutput = stdout_wr; startup_info.hStdError = stderr_wr; if(static_cast(options) != 0) { startup_info.dwFlags = STARTF_USESTDHANDLES; } #endif PROCESS_INFORMATION process_info{}; if(!CreateProcessW(std::data(native_path), null_or_data(args_str), nullptr, nullptr, TRUE, 0, nullptr, null_or_data(native_working_directory), &startup_info, &process_info)) { throw std::runtime_error{"Failed to create process. " + get_error_message()}; } m_id = static_cast(process_info.dwProcessId); m_handle = process_info.hProcess; m_thread_handle = process_info.hThread; #ifdef NES_PROCESS_PIPE_EXTENSION if(static_cast(options & process_options::grab_stdin)) { pipe_streambuf buffer{stdin_wr.release(), std::ios_base::out}; m_stdin_stream.reset(new pipe_ostream{std::move(buffer)}); } if(static_cast(options & process_options::grab_stdout)) { pipe_streambuf buffer{stdout_rd.release(), std::ios_base::in}; m_stdout_stream.reset(new pipe_istream{std::move(buffer)}); } if(static_cast(options & process_options::grab_stderr)) { pipe_streambuf buffer{stderr_rd.release(), std::ios_base::in}; m_stderr_stream.reset(new pipe_istream{std::move(buffer)}); } #endif } ~process() { assert(!joinable() && "nes::process::~process() called with joinable() returning true."); if(joinable()) { std::terminate(); } } process(const process&) = delete; process& operator=(const process&) = delete; process(process&& other) noexcept : m_id{std::exchange(other.m_id, id{})} , m_return_code{std::exchange(other.m_return_code, return_code_type{})} , m_handle{std::move(other.m_handle)} , m_thread_handle{std::move(other.m_thread_handle)} #ifdef NES_PROCESS_PIPE_EXTENSION , m_stdin_stream{std::move(other.m_stdin_stream)} , m_stdout_stream{std::move(other.m_stdout_stream)} , m_stderr_stream{std::move(other.m_stderr_stream)} #endif { } process& operator=(process&& other) noexcept { if(joinable()) { std::terminate(); } m_id = std::exchange(other.m_id, m_id); m_return_code = std::exchange(other.m_return_code, m_return_code); m_handle = std::move(other.m_handle); m_thread_handle = std::move(other.m_thread_handle); #ifdef NES_PROCESS_PIPE_EXTENSION m_stdin_stream = std::move(other.m_stdin_stream); m_stdout_stream = std::move(other.m_stdout_stream); m_stderr_stream = std::move(other.m_stderr_stream); #endif return *this; } void join() { assert(joinable() && "nes::process::join() called with joinable() returning false."); if(WaitForSingleObject(m_handle, INFINITE)) { throw std::runtime_error{"Failed to join the process. " + get_error_message()}; } if(!GetExitCodeProcess(m_handle, reinterpret_cast(&m_return_code))) { throw std::runtime_error{"Failed to get the return code of the process. " + get_error_message()}; } close_process(); } bool joinable() const noexcept { return m_handle; } bool active() const { if(!m_handle) { return false; } DWORD result = WaitForSingleObject(m_handle, 0); if(result == WAIT_FAILED) { throw std::runtime_error{"Failed to get the state of the process. " + get_error_message()}; } return result == WAIT_TIMEOUT; } void detach() { assert(joinable() && "nes::process::detach() called with joinable() returning false."); close_process(); } bool kill() { assert(joinable() && "nes::process::kill() called with joinable() returning false."); if(!TerminateProcess(m_handle, 1)) { return false; } join(); return true; } return_code_type return_code() const noexcept { assert(!joinable() && "nes::process::return_code() called with joinable() returning true."); return m_return_code; } native_handle_type native_handle() const noexcept { return m_handle; } id get_id() const noexcept { return m_id; } #ifdef NES_PROCESS_PIPE_EXTENSION pipe_ostream& stdin_stream() noexcept { return *m_stdin_stream; } pipe_istream& stdout_stream() noexcept { return *m_stdout_stream; } pipe_istream& stderr_stream() noexcept { return *m_stderr_stream; } #endif private: std::wstring to_wide(const std::string& path) { assert(std::size(path) < 0x7FFFFFFFu && "Wrong path."); if(std::empty(path)) { return {}; } std::wstring out_path{}; out_path.resize(static_cast(MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, std::data(path), static_cast(std::size(path)), nullptr, 0))); if(!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, std::data(path), static_cast(std::size(path)), std::data(out_path), static_cast(std::size(out_path)))) { throw std::runtime_error{"Failed to convert the path to wide."}; } return out_path; } std::string get_error_message() const { return "#" + std::to_string(GetLastError()); } void close_process() { m_id = id{}; CloseHandle(m_handle.release()); CloseHandle(m_thread_handle.release()); } wchar_t* null_or_data(std::wstring& str) { return std::empty(str) ? nullptr : std::data(str); } const wchar_t* null_or_data(const std::wstring& str) { return std::empty(str) ? nullptr : std::data(str); } private: id m_id{}; return_code_type m_return_code{}; impl::auto_handle m_handle{}; impl::auto_handle m_thread_handle{}; #ifdef NES_PROCESS_PIPE_EXTENSION std::unique_ptr m_stdin_stream{}; std::unique_ptr m_stdout_stream{}; std::unique_ptr m_stderr_stream{}; #endif }; namespace this_process { inline process::id get_id() noexcept { return process::id{GetCurrentProcessId()}; } inline std::string working_directory() { const DWORD size{GetCurrentDirectoryW(0, nullptr)}; std::wstring native_path{}; native_path.resize(static_cast(size)); GetCurrentDirectoryW(size, std::data(native_path)); native_path.pop_back(); // Because GetCurrentDirectoryW adds a null terminator std::transform(std::begin(native_path), std::end(native_path), std::begin(native_path), [](wchar_t c) { return c == L'\\' ? L'/' : c; }); std::string path{}; path.resize(static_cast(WideCharToMultiByte(CP_UTF8, 0, std::data(native_path), static_cast(std::size(native_path)), nullptr, 0, nullptr, nullptr))); if(!WideCharToMultiByte(CP_UTF8, 0, std::data(native_path), static_cast(std::size(native_path)), std::data(path), static_cast(std::size(path)), nullptr, nullptr)) { throw std::runtime_error{"Failed to convert the path to UTF-8."}; } return path; } inline bool change_working_directory(const std::string& path) { std::wstring native_path{}; native_path.resize(static_cast(MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, std::data(path), static_cast(std::size(path)), nullptr, 0))); if(!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, std::data(path), static_cast(std::size(path)), std::data(native_path), static_cast(std::size(native_path)))) { throw std::runtime_error{"Failed to convert the path to wide."}; } return SetCurrentDirectoryW(std::data(native_path)); } } NES_INLINE_NAMESPACE_END } template std::basic_ostream& operator<<(std::basic_ostream& os, nes::process::id id) { return os << static_cast(id); } namespace std { template<> struct hash { using argument_type = nes::process::id; using result_type = std::size_t; result_type operator()(const argument_type& s) const noexcept { return std::hash{}(static_cast(s)); } }; } #elif defined(NES_POSIX_PROCESS) namespace nes { NES_INLINE_NAMESPACE_BEGIN class process; namespace impl { enum class id_t : pid_t { }; constexpr bool operator==(id_t lhs, id_t rhs) noexcept { return static_cast(lhs) == static_cast(rhs); } constexpr bool operator!=(id_t lhs, id_t rhs) noexcept { return static_cast(lhs) != static_cast(rhs); } constexpr bool operator<(id_t lhs, id_t rhs) noexcept { return static_cast(lhs) < static_cast(rhs); } constexpr bool operator<=(id_t lhs, id_t rhs) noexcept { return static_cast(lhs) <= static_cast(rhs); } constexpr bool operator>(id_t lhs, id_t rhs) noexcept { return static_cast(lhs) > static_cast(rhs); } constexpr bool operator>=(id_t lhs, id_t rhs) noexcept { return static_cast(lhs) >= static_cast(rhs); } #ifdef NES_PROCESS_PIPE_EXTENSION struct auto_handle { constexpr auto_handle() = default; auto_handle(int h) : m_handle{h} { } ~auto_handle() { if(m_handle) { close(m_handle); } } auto_handle(const auto_handle&) = delete; auto_handle& operator=(const auto_handle&) = delete; auto_handle(auto_handle&& other) noexcept : m_handle{std::exchange(other.m_handle, -1)} { } auto_handle& operator=(auto_handle&& other) noexcept { m_handle = std::exchange(other.m_handle, m_handle); return *this; } int release() noexcept { return std::exchange(m_handle, -1); } operator int() noexcept { return m_handle; } operator int() const noexcept { return m_handle; } int* operator&() noexcept { return &m_handle; } const int* operator&() const noexcept { return &m_handle; } operator bool() const noexcept { return m_handle != -1; } int m_handle{-1}; }; #endif } enum class process_options : std::uint32_t { none = 0x00, #ifdef NES_PROCESS_PIPE_EXTENSION grab_stdout = 0x10, grab_stderr = 0x20, grab_stdin = 0x40 #endif }; constexpr process_options operator&(process_options left, process_options right) noexcept { return static_cast(static_cast(left) & static_cast(right)); } constexpr process_options& operator&=(process_options& left, process_options right) noexcept { left = left & right; return left; } constexpr process_options operator|(process_options left, process_options right) noexcept { return static_cast(static_cast(left) | static_cast(right)); } constexpr process_options& operator|=(process_options& left, process_options right) noexcept { left = left | right; return left; } constexpr process_options operator^(process_options left, process_options right) noexcept { return static_cast(static_cast(left) ^ static_cast(right)); } constexpr process_options& operator^=(process_options& left, process_options right) noexcept { left = left ^ right; return left; } constexpr process_options operator~(process_options value) noexcept { return static_cast(~static_cast(value)); } class process { public: using native_handle_type = pid_t; using return_code_type = int; using id = impl::id_t; public: constexpr process() noexcept = default; explicit process(const std::string& path, const std::string& working_directory) : process{path, {}, working_directory, {}} { } explicit process(const std::string& path, process_options options) : process{path, {}, {}, options} { } explicit process(const std::string& path, const std::vector& args, process_options options) : process{path, args, {}, options} { } explicit process(const std::string& path, const std::string& working_directory, process_options options) : process{path, {}, working_directory, options} { } explicit process(const std::string& path, std::vector args = std::vector{}, const std::string& working_directory = std::string{}, process_options options [[maybe_unused]] = process_options{}) { assert(!std::empty(path) && "nes::process::process called with empty path."); args.insert(std::begin(args), path); std::vector native_args{}; native_args.resize(std::size(args) + 1); for(std::size_t i{}; i < std::size(args); ++i) { native_args[i] = std::data(args[i]); } #ifdef NES_PROCESS_PIPE_EXTENSION impl::auto_handle stdin_fd[2]{}; impl::auto_handle stdout_fd[2]{}; impl::auto_handle stderr_fd[2]{}; if(static_cast(options & process_options::grab_stdin) && pipe(reinterpret_cast(stdin_fd))) { throw std::runtime_error{"Failed to create stdin pipe. " + std::string{strerror(errno)}}; } if(static_cast(options & process_options::grab_stdout) && pipe(reinterpret_cast(stdout_fd))) { throw std::runtime_error{"Failed to create stdout pipe. " + std::string{strerror(errno)}}; } if(static_cast(options & process_options::grab_stderr) && pipe(reinterpret_cast(stderr_fd))) { throw std::runtime_error{"Failed to create stderr pipe. " + std::string{strerror(errno)}}; } const bool standard_streams{static_cast(options & process_options::grab_stdin) || static_cast(options & process_options::grab_stdout) || static_cast(options & process_options::grab_stderr)}; #else constexpr bool standard_streams{false}; #endif const pid_t id{fork()}; if(id < 0) { throw std::runtime_error{"Failed to create process. " + std::string{strerror(errno)}}; } else if(id == 0) { #ifdef NES_PROCESS_PIPE_EXTENSION if(static_cast(options & process_options::grab_stdin)) { if(dup2(stdin_fd[0], 0) == -1) { _exit(EXIT_FAILURE); } } if(static_cast(options & process_options::grab_stdout)) { if(dup2(stdout_fd[1], 1) == -1) { _exit(EXIT_FAILURE); } } if(static_cast(options & process_options::grab_stderr)) { if(dup2(stderr_fd[1], 2) == -1) { _exit(EXIT_FAILURE); } } #endif if(!std::empty(working_directory)) { if(chdir(std::data(working_directory))) { _exit(EXIT_FAILURE); } } execv(std::data(path), std::data(native_args)); _exit(EXIT_FAILURE); } m_id = id; #ifdef NES_PROCESS_PIPE_EXTENSION if(static_cast(options & process_options::grab_stdin)) { pipe_streambuf buf{stdin_fd[1].release(), std::ios_base::out}; m_stdin_stream.reset(new pipe_ostream{std::move(buf)}); } if(static_cast(options & process_options::grab_stdout)) { pipe_streambuf buf{stdout_fd[0].release(), std::ios_base::in}; m_stdout_stream.reset(new pipe_istream{std::move(buf)}); } if(static_cast(options & process_options::grab_stderr)) { pipe_streambuf buf{stderr_fd[0].release(), std::ios_base::in}; m_stderr_stream.reset(new pipe_istream{std::move(buf)}); } #endif } ~process() { assert(!joinable() && "nes::process::~process() called with joinable() returning true."); if(joinable()) { std::terminate(); } } process(const process&) = delete; process& operator=(const process&) = delete; process(process&& other) noexcept : m_id{std::exchange(other.m_id, -1)} , m_return_code{std::exchange(other.m_return_code, return_code_type{})} #ifdef NES_PROCESS_PIPE_EXTENSION , m_stdin_stream{std::move(other.m_stdin_stream)} , m_stdout_stream{std::move(other.m_stdout_stream)} , m_stderr_stream{std::move(other.m_stderr_stream)} #endif { } process& operator=(process&& other) noexcept { if(joinable()) { std::terminate(); } m_id = std::exchange(other.m_id, m_id); m_return_code = std::exchange(other.m_return_code, m_return_code); #ifdef NES_PROCESS_PIPE_EXTENSION m_stdin_stream = std::move(other.m_stdin_stream); m_stdout_stream = std::move(other.m_stdout_stream); m_stderr_stream = std::move(other.m_stderr_stream); #endif return *this; } void join() { assert(joinable() && "nes::process::join() called with joinable() returning false."); int return_code{}; if(waitpid(m_id, &return_code, 0) == -1) { throw std::runtime_error{"Failed to join the process. " + std::string{strerror(errno)}}; } m_id = -1; m_return_code = WEXITSTATUS(return_code); } bool joinable() const noexcept { return m_id != -1; } bool active() const { return ::kill(m_id, 0) != ESRCH; } void detach() { assert(joinable() && "nes::process::detach() called with joinable() returning false."); m_id = -1; } bool kill() { assert(joinable() && "nes::process::kill() called with joinable() returning false."); if(::kill(m_id, SIGTERM)) { return false; } join(); return true; } return_code_type return_code() const noexcept { assert(!joinable() && "nes::process::return_code() called with joinable() returning true."); return m_return_code; } native_handle_type native_handle() const noexcept { return m_id; } id get_id() const noexcept { return static_cast(m_id); } #ifdef NES_PROCESS_PIPE_EXTENSION pipe_ostream& stdin_stream() noexcept { return *m_stdin_stream; } pipe_istream& stdout_stream() noexcept { return *m_stdout_stream; } pipe_istream& stderr_stream() noexcept { return *m_stderr_stream; } #endif private: native_handle_type m_id{}; return_code_type m_return_code{}; #ifdef NES_PROCESS_PIPE_EXTENSION std::unique_ptr m_stdin_stream{}; std::unique_ptr m_stdout_stream{}; std::unique_ptr m_stderr_stream{}; #endif }; namespace this_process { inline process::id get_id() noexcept { return process::id{getpid()}; } inline std::string working_directory() { std::string path{}; path.resize(256); while(!getcwd(std::data(path), std::size(path))) { if(errno == ERANGE) { path.resize(std::size(path) * 2); } else { throw std::runtime_error{"Failed to get the current working directory. " + std::string{strerror(errno)}}; } } path.resize(path.find_first_of('\0')); return path; } inline bool change_working_directory(const std::string& path) { return chdir(std::data(path)) == 0; } } NES_INLINE_NAMESPACE_END } template std::basic_ostream& operator<<(std::basic_ostream& os, nes::process::id id) { return os << static_cast(id); } namespace std { template<> struct hash { using argument_type = nes::process::id; using result_type = std::size_t; result_type operator()(const argument_type& s) const noexcept { return std::hash{}(static_cast(s)); } }; } #endif #endif ================================================ FILE: include/nes/semaphore.hpp ================================================ /////////////////////////////////////////////////////////// /// Copyright 2019 Alexy Pellegrini /// /// Permission is hereby granted, free of charge, /// to any person obtaining a copy of this software /// and associated documentation files (the "Software"), /// to deal in the Software without restriction, /// including without limitation the rights to use, /// copy, modify, merge, publish, distribute, sublicense, /// and/or sell copies of the Software, and to permit /// persons to whom the Software is furnished to do so, /// subject to the following conditions: /// /// The above copyright notice and this permission notice /// shall be included in all copies or substantial portions /// of the Software. /// /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF /// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED /// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A /// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT /// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR /// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN /// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE /// OR OTHER DEALINGS IN THE SOFTWARE. /////////////////////////////////////////////////////////// #ifndef NOT_ENOUGH_STANDARDS_SEMAPHORE #define NOT_ENOUGH_STANDARDS_SEMAPHORE #if defined(_WIN32) #define NES_WIN32_SEMAPHORE #ifndef NOMINMAX #define NOMINMAX #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #elif defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) #define NES_POSIX_SEMAPHORE #include #include #include #else #error "Not enough standards does not support this environment." #endif #ifdef NES_INLINE_NAMESPACE #define NES_INLINE_NAMESPACE_BEGIN \ inline namespace NES_INLINE_NAMESPACE \ { #define NES_INLINE_NAMESPACE_END } #else #define NES_INLINE_NAMESPACE_BEGIN #define NES_INLINE_NAMESPACE_END #endif #include #include #include #include #include #include #if defined(NES_WIN32_SEMAPHORE) namespace nes { NES_INLINE_NAMESPACE_BEGIN class semaphore { public: using native_handle_type = HANDLE; public: explicit semaphore(std::size_t initial_count = 0) { m_handle = CreateSemaphoreW(nullptr, static_cast(initial_count), std::numeric_limits::max(), nullptr); if(!m_handle) { throw std::runtime_error{"Failed to create semaphore. " + get_error_message()}; } } ~semaphore() { if(m_handle) { CloseHandle(m_handle); } } semaphore(const semaphore&) = delete; semaphore& operator=(const semaphore&) = delete; semaphore(semaphore&& other) noexcept = delete; semaphore& operator=(semaphore&& other) noexcept = delete; void acquire() { if(WaitForSingleObject(m_handle, INFINITE)) { throw std::runtime_error{"Failed to decrement semaphore count. " + get_error_message()}; } } bool try_acquire() { return WaitForSingleObject(m_handle, 0) == WAIT_OBJECT_0; } void release() { if(!ReleaseSemaphore(m_handle, 1, nullptr)) { throw std::runtime_error{"Failed to increment semaphore count. " + get_error_message()}; } } native_handle_type native_handle() const noexcept { return m_handle; } private: std::string get_error_message() const { return "#" + std::to_string(GetLastError()); } private: native_handle_type m_handle{}; }; class timed_semaphore { public: using native_handle_type = HANDLE; public: explicit timed_semaphore(std::size_t initial_count = 0) { m_handle = CreateSemaphoreW(nullptr, static_cast(initial_count), std::numeric_limits::max(), nullptr); if(!m_handle) { throw std::runtime_error{"Failed to create semaphore. " + get_error_message()}; } } ~timed_semaphore() { CloseHandle(m_handle); } timed_semaphore(const timed_semaphore&) = delete; timed_semaphore& operator=(const timed_semaphore&) = delete; timed_semaphore(timed_semaphore&& other) noexcept = delete; timed_semaphore& operator=(timed_semaphore&& other) noexcept = delete; void acquire() { if(WaitForSingleObject(m_handle, INFINITE)) { throw std::runtime_error{"Failed to decrement semaphore count. " + get_error_message()}; } } bool try_acquire() { return WaitForSingleObject(m_handle, 0) == WAIT_OBJECT_0; } template bool try_acquire_for(const std::chrono::duration& timeout) { return WaitForSingleObject(m_handle, std::chrono::duration_cast(timeout).count()) == WAIT_OBJECT_0; } template bool try_acquire_until(const std::chrono::time_point& time_point) { const auto current_time{Clock::now()}; if(time_point < current_time) { return try_acquire(); } return try_acquire_for(time_point - current_time); } void release() { if(!ReleaseSemaphore(m_handle, 1, nullptr)) { throw std::runtime_error{"Failed to increment semaphore count. " + get_error_message()}; } } native_handle_type native_handle() const noexcept { return m_handle; } private: std::string get_error_message() const { return "#" + std::to_string(GetLastError()); } private: native_handle_type m_handle{}; }; NES_INLINE_NAMESPACE_END } #elif defined(NES_POSIX_SEMAPHORE) namespace nes { NES_INLINE_NAMESPACE_BEGIN class semaphore { public: using native_handle_type = sem_t*; public: explicit semaphore(std::size_t initial_count = 0) { if(sem_init(m_handle.get(), 0, initial_count) != 0) { throw std::runtime_error{"Failed to create semaphore. " + std::string{strerror(errno)}}; } } ~semaphore() { sem_destroy(m_handle.get()); } semaphore(const semaphore&) = delete; semaphore& operator=(const semaphore&) = delete; semaphore(semaphore&& other) noexcept = delete; semaphore& operator=(semaphore&& other) noexcept = delete; void acquire() { if(sem_wait(m_handle.get()) == -1) { throw std::runtime_error{"Failed to decrement semaphore count. " + std::string{strerror(errno)}}; } } bool try_acquire() { return !sem_trywait(m_handle.get()); } void release() { if(sem_post(m_handle.get()) == -1) { throw std::runtime_error{"Failed to increment semaphore count. " + std::string{strerror(errno)}}; } } native_handle_type native_handle() const noexcept { return m_handle.get(); } private: std::unique_ptr m_handle{std::make_unique()}; }; class timed_semaphore { public: using native_handle_type = sem_t*; public: explicit timed_semaphore(std::size_t initial_count = 0) { if(sem_init(m_handle.get(), 0, initial_count) != 0) { throw std::runtime_error{"Failed to create timed_semaphore. " + std::string{strerror(errno)}}; } } ~timed_semaphore() { sem_destroy(m_handle.get()); } timed_semaphore(const timed_semaphore&) = delete; timed_semaphore& operator=(const timed_semaphore&) = delete; timed_semaphore(timed_semaphore&& other) noexcept = delete; timed_semaphore& operator=(timed_semaphore&& other) noexcept = delete; void acquire() { if(sem_wait(m_handle.get()) == -1) { throw std::runtime_error{"Failed to decrement semaphore count. " + std::string{strerror(errno)}}; } } bool try_acquire() { return !sem_trywait(m_handle.get()); } template bool try_lock_for(const std::chrono::duration& timeout) { return try_lock_until(std::chrono::system_clock::now() + timeout); } template bool try_lock_until(const std::chrono::time_point& time_point) { const auto seconds{std::chrono::time_point_cast(time_point)}; const auto nanoseconds{std::chrono::duration_cast(time_point - seconds)}; timespec time{}; time.tv_sec = static_cast(seconds.time_since_epoch().count()); time.tv_nsec = static_cast(nanoseconds.count()); return !sem_timedwait(m_handle.get(), &time); } void release() { if(sem_post(m_handle.get()) == -1) { throw std::runtime_error{"Failed to increment semaphore count. " + std::string{strerror(errno)}}; } } native_handle_type native_handle() const noexcept { return m_handle.get(); } private: std::unique_ptr m_handle{std::make_unique()}; }; NES_INLINE_NAMESPACE_END } #endif #endif ================================================ FILE: include/nes/shared_library.hpp ================================================ /////////////////////////////////////////////////////////// /// Copyright 2019 Alexy Pellegrini /// /// Permission is hereby granted, free of charge, /// to any person obtaining a copy of this software /// and associated documentation files (the "Software"), /// to deal in the Software without restriction, /// including without limitation the rights to use, /// copy, modify, merge, publish, distribute, sublicense, /// and/or sell copies of the Software, and to permit /// persons to whom the Software is furnished to do so, /// subject to the following conditions: /// /// The above copyright notice and this permission notice /// shall be included in all copies or substantial portions /// of the Software. /// /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF /// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED /// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A /// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT /// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR /// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN /// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE /// OR OTHER DEALINGS IN THE SOFTWARE. /////////////////////////////////////////////////////////// #ifndef NOT_ENOUGH_STANDARDS_SHARED_LIBRARY #define NOT_ENOUGH_STANDARDS_SHARED_LIBRARY #if defined(_WIN32) #define NES_WIN32_SHARED_LIBRARY #ifndef NOMINMAX #define NOMINMAX #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #elif defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) #define NES_POSIX_SHARED_LIBRARY #include #else #error "Not enough standards does not support this environment." #endif #ifdef NES_INLINE_NAMESPACE #define NES_INLINE_NAMESPACE_BEGIN \ inline namespace NES_INLINE_NAMESPACE \ { #define NES_INLINE_NAMESPACE_END } #else #define NES_INLINE_NAMESPACE_BEGIN #define NES_INLINE_NAMESPACE_END #endif #include #include #include #include #include #if defined(NES_WIN32_SHARED_LIBRARY) namespace nes { NES_INLINE_NAMESPACE_BEGIN struct load_current_t { }; inline constexpr load_current_t load_current{}; class shared_library { public: using native_handle_type = HINSTANCE; public: constexpr shared_library() noexcept = default; explicit shared_library(load_current_t) { m_handle = GetModuleHandleW(nullptr); if(!m_handle) { throw std::runtime_error{"Failed to load current binary file. " + get_error_message()}; } } explicit shared_library(const std::string& path) { assert(!std::empty(path) && "nes::shared_library::shared_library called with empty path."); m_handle = LoadLibraryW(to_wide(path).c_str()); if(!m_handle) { throw std::runtime_error{"Failed to load binary file \"" + path + "\". " + get_error_message()}; } m_need_free = true; } ~shared_library() { if(m_handle && m_need_free) { FreeLibrary(m_handle); } } shared_library(const shared_library&) = delete; shared_library& operator=(const shared_library&) = delete; shared_library(shared_library&& other) noexcept : m_handle{std::exchange(other.m_handle, native_handle_type{})} , m_need_free{std::exchange(other.m_need_free, false)} { } shared_library& operator=(shared_library&& other) noexcept { m_handle = std::exchange(other.m_handle, m_handle); m_need_free = std::exchange(other.m_need_free, m_need_free); return *this; } template && std::is_function_v>>> Func load(const std::string& symbol) const noexcept { assert(!std::empty(symbol) && "nes::shared_library::load called with an empty symbol name."); assert(m_handle && "nes::shared_library::load called with invalid handle."); return reinterpret_cast(reinterpret_cast(GetProcAddress(m_handle, std::data(symbol)))); } template>> Func* load(const std::string& symbol) const noexcept { return load(symbol); } native_handle_type native_handle() const noexcept { return m_handle; } private: std::wstring to_wide(std::string path) { assert(std::size(path) < 0x7FFFFFFFu && "Wrong path."); std::transform(std::begin(path), std::end(path), std::begin(path), [](char c) { return c == '/' ? '\\' : c; }); std::wstring out_path{}; out_path.resize(static_cast(MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, std::data(path), static_cast(std::size(path)), nullptr, 0))); if(!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, std::data(path), static_cast(std::size(path)), std::data(out_path), static_cast(std::size(out_path)))) { throw std::runtime_error{"Failed to convert the path to wide."}; } return out_path; } std::string get_error_message() const { return "#" + std::to_string(GetLastError()); } native_handle_type m_handle{}; bool m_need_free{}; }; NES_INLINE_NAMESPACE_END } #elif defined(NES_POSIX_SHARED_LIBRARY) namespace nes { NES_INLINE_NAMESPACE_BEGIN struct load_current_t { }; inline constexpr load_current_t load_current{}; class shared_library { public: using native_handle_type = void*; public: constexpr shared_library() noexcept = default; explicit shared_library(load_current_t) { m_handle = dlopen(nullptr, RTLD_NOW); if(!m_handle) { throw std::runtime_error{"Failed to load current binary file. " + std::string{dlerror()}}; } } explicit shared_library(const std::string& path) { assert(!std::empty(path) && "nes::shared_library::shared_library called with empty path."); m_handle = dlopen(std::data(path), RTLD_NOW); if(!m_handle) { throw std::runtime_error{"Failed to load binary file \"" + path + "\". " + std::string{dlerror()}}; } } ~shared_library() { if(m_handle) { dlclose(m_handle); } } shared_library(const shared_library&) = delete; shared_library& operator=(const shared_library&) = delete; shared_library(shared_library&& other) noexcept : m_handle{std::exchange(other.m_handle, native_handle_type{})} { } shared_library& operator=(shared_library&& other) noexcept { m_handle = std::exchange(other.m_handle, m_handle); return *this; } template && std::is_function_v>>> Func load(const std::string& symbol) const noexcept { assert(!std::empty(symbol) && "nes::shared_library::load called with an empty symbol name."); assert(m_handle && "nes::shared_library::load called with invalid handle."); return reinterpret_cast(dlsym(m_handle, std::data(symbol))); } template>> Func* load(const std::string& symbol) const noexcept { return load(symbol); } native_handle_type native_handle() const noexcept { return m_handle; } private: native_handle_type m_handle{}; }; NES_INLINE_NAMESPACE_END } #endif #endif ================================================ FILE: include/nes/shared_memory.hpp ================================================ /////////////////////////////////////////////////////////// /// Copyright 2019 Alexy Pellegrini /// /// Permission is hereby granted, free of charge, /// to any person obtaining a copy of this software /// and associated documentation files (the "Software"), /// to deal in the Software without restriction, /// including without limitation the rights to use, /// copy, modify, merge, publish, distribute, sublicense, /// and/or sell copies of the Software, and to permit /// persons to whom the Software is furnished to do so, /// subject to the following conditions: /// /// The above copyright notice and this permission notice /// shall be included in all copies or substantial portions /// of the Software. /// /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF /// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED /// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A /// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT /// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR /// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN /// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE /// OR OTHER DEALINGS IN THE SOFTWARE. /////////////////////////////////////////////////////////// #ifndef NOT_ENOUGH_STANDARDS_SHARED_MEMORY #define NOT_ENOUGH_STANDARDS_SHARED_MEMORY #if defined(_WIN32) #define NES_WIN32_SHARED_MEMORY #ifndef NOMINMAX #define NOMINMAX #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #elif defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) #define NES_POSIX_SHARED_MEMORY #include #include #include #include #include #else #error "Not enough standards does not support this environment." #endif #include #include #include #include #include #if defined(NES_WIN32_SHARED_MEMORY) namespace nes { inline constexpr const char shared_memory_root[] = "Local\\"; enum class shared_memory_options : std::uint32_t { none = 0x00, constant = 0x01 }; constexpr shared_memory_options operator&(shared_memory_options left, shared_memory_options right) noexcept { return static_cast(static_cast(left) & static_cast(right)); } constexpr shared_memory_options& operator&=(shared_memory_options& left, shared_memory_options right) noexcept { left = left & right; return left; } constexpr shared_memory_options operator|(shared_memory_options left, shared_memory_options right) noexcept { return static_cast(static_cast(left) | static_cast(right)); } constexpr shared_memory_options& operator|=(shared_memory_options& left, shared_memory_options right) noexcept { left = left | right; return left; } constexpr shared_memory_options operator^(shared_memory_options left, shared_memory_options right) noexcept { return static_cast(static_cast(left) ^ static_cast(right)); } constexpr shared_memory_options& operator^=(shared_memory_options& left, shared_memory_options right) noexcept { left = left ^ right; return left; } constexpr shared_memory_options operator~(shared_memory_options value) noexcept { return static_cast(~static_cast(value)); } namespace impl { inline std::uintptr_t get_allocation_granularity() noexcept { SYSTEM_INFO info{}; GetSystemInfo(&info); return info.dwAllocationGranularity; } static const std::uintptr_t allocation_granularity_mask{~(get_allocation_granularity() - 1)}; template struct is_unbounded_array : std::false_type { }; template struct is_unbounded_array : std::true_type { }; template struct is_bounded_array : std::false_type { }; template struct is_bounded_array : std::true_type { }; } template struct map_deleter { void operator()(T* ptr) const noexcept { if(ptr) { UnmapViewOfFile(reinterpret_cast(reinterpret_cast(ptr) & impl::allocation_granularity_mask)); } } }; template struct map_deleter { void operator()(T* ptr) const noexcept { if(ptr) { UnmapViewOfFile(reinterpret_cast(reinterpret_cast(ptr) & impl::allocation_granularity_mask)); } } }; template using unique_map_t = std::unique_ptr>; template using shared_map_t = std::shared_ptr; template using weak_map_t = std::weak_ptr; class shared_memory { public: using native_handle_type = HANDLE; public: constexpr shared_memory() noexcept = default; explicit shared_memory(const std::string& name, std::uint64_t size) { assert(!std::empty(name) && "nes::shared_memory::shared_memory called with empty name."); assert(size != 0 && "nes::shared_memory::shared_memory called with size == 0."); const auto native_name{to_wide(shared_memory_root + name)}; m_handle = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, static_cast(size >> 32), static_cast(size), std::data(native_name)); if(!m_handle || GetLastError() == ERROR_ALREADY_EXISTS) { throw std::runtime_error{"Failed to create shared memory. " + get_error_message()}; } } explicit shared_memory(const std::string& name, shared_memory_options options = shared_memory_options::none) { assert(!std::empty(name) && "nes::shared_memory::shared_memory called with empty name."); const auto native_name{to_wide(shared_memory_root + name)}; const DWORD access = static_cast(options & shared_memory_options::constant) ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS; m_handle = OpenFileMappingW(access, FALSE, std::data(native_name)); if(!m_handle) { throw std::runtime_error{"Failed to open shared memory. " + get_error_message()}; } } ~shared_memory() { if(m_handle) { CloseHandle(m_handle); } } shared_memory(const shared_memory&) = delete; shared_memory& operator=(const shared_memory&) = delete; shared_memory(shared_memory&& other) noexcept : m_handle{std::exchange(other.m_handle, nullptr)} { } shared_memory& operator=(shared_memory&& other) noexcept { m_handle = std::exchange(other.m_handle, m_handle); return *this; } template unique_map_t map(std::uint64_t offset, shared_memory_options options = (std::is_const::value ? shared_memory_options::constant : shared_memory_options::none)) const { static_assert(std::is_trivial::value, "Behaviour is undefined if T is not a trivial type."); static_assert(!impl::is_unbounded_array::value, "T can not be an unbounded array type, i.e. T[]. Specify the size, or use the second overload if you don't know it at compile-time"); assert(m_handle && "nes::shared_memory::map called with an invalid handle."); const DWORD access = static_cast(options & shared_memory_options::constant) ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS; const auto aligned_offset{offset & impl::allocation_granularity_mask}; const auto real_size{static_cast((offset - aligned_offset) + sizeof(T))}; auto* ptr{MapViewOfFile(m_handle, access, static_cast(aligned_offset >> 32), static_cast(aligned_offset), real_size)}; if(!ptr) { throw std::runtime_error{"Failed to map shared memory. " + get_error_message()}; } ptr = reinterpret_cast(reinterpret_cast(ptr) + (offset - aligned_offset)); return unique_map_t{static_cast(ptr)}; } template shared_map_t shared_map(std::uint64_t offset, shared_memory_options options = (std::is_const::value ? shared_memory_options::constant : shared_memory_options::none)) const { return shared_map_t{map(offset, options)}; } template::type> unique_map_t map(std::uint64_t offset, std::size_t count, shared_memory_options options = (std::is_const::value ? shared_memory_options::constant : shared_memory_options::none)) const { static_assert(std::is_trivial::value, "Behaviour is undefined if ValueType is not a trivial type."); static_assert(!impl::is_bounded_array::value, "T is an statically sized array, use the other overload of map instead of this one (remove the second parameter)."); static_assert(impl::is_unbounded_array::value, "T must be an array type, i.e. T[]."); assert(m_handle && "nes::shared_memory::map called with an invalid handle."); const DWORD access = static_cast(options & shared_memory_options::constant) ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS; const auto aligned_offset{offset & impl::allocation_granularity_mask}; const auto real_size{static_cast((offset - aligned_offset) + (sizeof(ValueType) * count))}; auto* ptr{MapViewOfFile(m_handle, access, static_cast(aligned_offset >> 32), static_cast(aligned_offset), real_size)}; if(!ptr) { throw std::runtime_error{"Failed to map shared memory. " + get_error_message()}; } ptr = reinterpret_cast(reinterpret_cast(ptr) + (offset - aligned_offset)); return unique_map_t{static_cast(ptr)}; } template::type> shared_map_t shared_map(std::uint64_t offset, std::size_t count, shared_memory_options options = (std::is_const::value ? shared_memory_options::constant : shared_memory_options::none)) const { return shared_map_t{map(offset, count, options)}; } native_handle_type native_handle() const noexcept { return m_handle; } private: std::wstring to_wide(const std::string& path) { assert(std::size(path) < 0x7FFFFFFFu && "Wrong path."); if(std::empty(path)) { return {}; } std::wstring out_path{}; out_path.resize(static_cast(MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, std::data(path), static_cast(std::size(path)), nullptr, 0))); if(!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, std::data(path), static_cast(std::size(path)), std::data(out_path), static_cast(std::size(out_path)))) { throw std::runtime_error{"Failed to convert the path to wide."}; } return out_path; } std::string get_error_message() const { return "#" + std::to_string(GetLastError()); } private: native_handle_type m_handle{}; }; } #elif defined(NES_POSIX_SHARED_MEMORY) namespace nes { inline constexpr const char shared_memory_root[] = "/"; enum class shared_memory_options : std::uint32_t { none = 0x00, constant = 0x01 }; constexpr shared_memory_options operator&(shared_memory_options left, shared_memory_options right) noexcept { return static_cast(static_cast(left) & static_cast(right)); } constexpr shared_memory_options& operator&=(shared_memory_options& left, shared_memory_options right) noexcept { left = left & right; return left; } constexpr shared_memory_options operator|(shared_memory_options left, shared_memory_options right) noexcept { return static_cast(static_cast(left) | static_cast(right)); } constexpr shared_memory_options& operator|=(shared_memory_options& left, shared_memory_options right) noexcept { left = left | right; return left; } constexpr shared_memory_options operator^(shared_memory_options left, shared_memory_options right) noexcept { return static_cast(static_cast(left) ^ static_cast(right)); } constexpr shared_memory_options& operator^=(shared_memory_options& left, shared_memory_options right) noexcept { left = left ^ right; return left; } constexpr shared_memory_options operator~(shared_memory_options value) noexcept { return static_cast(~static_cast(value)); } namespace impl { inline std::uintptr_t get_allocation_granularity() noexcept { return static_cast(sysconf(_SC_PAGE_SIZE)); } static const std::uintptr_t allocation_granularity_mask{~(get_allocation_granularity() - 1)}; template struct is_unbounded_array : std::false_type { }; template struct is_unbounded_array : std::true_type { }; template struct is_bounded_array : std::false_type { }; template struct is_bounded_array : std::true_type { }; } template struct map_deleter { void operator()(T* ptr) const noexcept { if(ptr) { const auto base_address{reinterpret_cast(ptr) & impl::allocation_granularity_mask}; munmap(reinterpret_cast(base_address), static_cast(reinterpret_cast(ptr) - base_address) + sizeof(T)); } } }; template struct map_deleter { void operator()(T* ptr) const noexcept { if(ptr) { const auto base_address{reinterpret_cast(ptr) & impl::allocation_granularity_mask}; munmap(reinterpret_cast(base_address), static_cast(reinterpret_cast(ptr) - base_address) + (sizeof(T) * count)); } } std::size_t count{}; }; template using unique_map_t = std::unique_ptr>; template using shared_map_t = std::shared_ptr; template using weak_map_t = std::weak_ptr; class shared_memory { public: using native_handle_type = int; public: constexpr shared_memory() noexcept = default; explicit shared_memory(const std::string& name, std::uint64_t size) { assert(!std::empty(name) && "nes::shared_memory::shared_memory called with empty name."); assert(size != 0 && "nes::shared_memory::shared_memory called with size == 0."); const auto native_name{shared_memory_root + name}; m_handle = shm_open(std::data(native_name), O_RDWR | O_CREAT | O_TRUNC, 0660); if(m_handle == -1) { throw std::runtime_error{"Failed to create shared memory. " + std::string{strerror(errno)}}; } if(ftruncate(m_handle, static_cast(size)) == -1) { close(m_handle); throw std::runtime_error{"Failed to set shared memory size. " + std::string{strerror(errno)}}; } } explicit shared_memory(const std::string& name, shared_memory_options options = shared_memory_options::none) { assert(!std::empty(name) && "nes::shared_memory::shared_memory called with empty name."); const auto native_name{shared_memory_root + name}; const auto access = static_cast(options & shared_memory_options::constant) ? O_RDONLY : O_RDWR; m_handle = shm_open(std::data(native_name), access, 0660); if(m_handle == -1) { throw std::runtime_error{"Failed to open shared memory. " + std::string{strerror(errno)}}; } } ~shared_memory() { if(m_handle != -1) { close(m_handle); } } shared_memory(const shared_memory&) = delete; shared_memory& operator=(const shared_memory&) = delete; shared_memory(shared_memory&& other) noexcept : m_handle{std::exchange(other.m_handle, -1)} { } shared_memory& operator=(shared_memory&& other) noexcept { m_handle = std::exchange(other.m_handle, m_handle); return *this; } template unique_map_t map(std::uint64_t offset, shared_memory_options options = (std::is_const::value ? shared_memory_options::constant : shared_memory_options::none)) const { static_assert(std::is_trivial::value, "Behaviour is undefined if T is not a trivial type."); static_assert(!impl::is_unbounded_array::value, "T can not be an unbounded array type, i.e. T[]. Specify the size, or use the second overload if you don't know it at compile-time"); assert(m_handle != -1 && "nes::shared_memory::map called with an invalid handle."); const auto access = static_cast(options & shared_memory_options::constant) ? PROT_READ : PROT_READ | PROT_WRITE; const auto aligned_offset{offset & impl::allocation_granularity_mask}; const auto real_size{static_cast((offset - aligned_offset) + sizeof(T))}; auto* ptr{mmap(nullptr, real_size, access, MAP_SHARED, m_handle, static_cast(aligned_offset))}; if(ptr == MAP_FAILED) { throw std::runtime_error{"Failed to map shared memory. " + std::string{strerror(errno)}}; } ptr = reinterpret_cast(reinterpret_cast(ptr) + (offset - aligned_offset)); return unique_map_t{reinterpret_cast(ptr)}; } template shared_map_t shared_map(std::uint64_t offset, shared_memory_options options = (std::is_const::value ? shared_memory_options::constant : shared_memory_options::none)) const { return shared_map_t{map(offset, options)}; } template::type> unique_map_t map(std::uint64_t offset, std::size_t count, shared_memory_options options = (std::is_const::value ? shared_memory_options::constant : shared_memory_options::none)) const { static_assert(std::is_trivial::value, "Behaviour is undefined if ValueType is not a trivial type."); static_assert(!impl::is_bounded_array::value, "T is an statically sized array, use the other overload of map instead of this one (remove the second parameter)."); static_assert(impl::is_unbounded_array::value, "T must be an array type, i.e. T[]."); assert(m_handle != -1 && "nes::shared_memory::map called with an invalid handle."); const auto access = static_cast(options & shared_memory_options::constant) ? PROT_READ : PROT_READ | PROT_WRITE; const auto aligned_offset{offset & impl::allocation_granularity_mask}; const auto real_size{static_cast((offset - aligned_offset) + (sizeof(ValueType) * count))}; auto* ptr{mmap(nullptr, real_size, access, MAP_SHARED, m_handle, static_cast(aligned_offset))}; if(ptr == MAP_FAILED) { throw std::runtime_error{"Failed to map shared memory. " + std::string{strerror(errno)}}; } ptr = reinterpret_cast(reinterpret_cast(ptr) + (offset - aligned_offset)); return unique_map_t{reinterpret_cast(ptr), map_deleter{count}}; } template::type> shared_map_t shared_map(std::uint64_t offset, std::size_t count, shared_memory_options options = (std::is_const::value ? shared_memory_options::constant : shared_memory_options::none)) const { return shared_map_t{map(offset, count, options)}; } native_handle_type native_handle() const noexcept { return m_handle; } private: native_handle_type m_handle{-1}; }; } #endif #endif ================================================ FILE: include/nes/thread_pool.hpp ================================================ /////////////////////////////////////////////////////////// /// Copyright 2020 Alexy Pellegrini /// /// Permission is hereby granted, free of charge, /// to any person obtaining a copy of this software /// and associated documentation files (the "Software"), /// to deal in the Software without restriction, /// including without limitation the rights to use, /// copy, modify, merge, publish, distribute, sublicense, /// and/or sell copies of the Software, and to permit /// persons to whom the Software is furnished to do so, /// subject to the following conditions: /// /// The above copyright notice and this permission notice /// shall be included in all copies or substantial portions /// of the Software. /// /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF /// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED /// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A /// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT /// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR /// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN /// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE /// OR OTHER DEALINGS IN THE SOFTWARE. /////////////////////////////////////////////////////////// #ifndef NOT_ENOUGH_STANDARDS_THREAD_POOL #define NOT_ENOUGH_STANDARDS_THREAD_POOL #ifdef NES_INLINE_NAMESPACE #define NES_INLINE_NAMESPACE_BEGIN \ inline namespace NES_INLINE_NAMESPACE \ { #define NES_INLINE_NAMESPACE_END } #else #define NES_INLINE_NAMESPACE_BEGIN #define NES_INLINE_NAMESPACE_END #endif #include #include #include #include #include #include #include #include #include #include #include #include namespace nes { NES_INLINE_NAMESPACE_BEGIN namespace impl { class checkpoint_holder_base { public: checkpoint_holder_base(bool barrier) : m_barrier{barrier} { } virtual ~checkpoint_holder_base() = default; checkpoint_holder_base(const checkpoint_holder_base&) = delete; checkpoint_holder_base& operator=(const checkpoint_holder_base&) = delete; checkpoint_holder_base(checkpoint_holder_base&&) = delete; checkpoint_holder_base& operator=(checkpoint_holder_base&&) = delete; virtual void set_reset_value(std::size_t value) noexcept = 0; virtual void reset() = 0; virtual bool count_down() noexcept = 0; virtual bool check_barrier() const noexcept = 0; bool is_barrier() const noexcept { return m_barrier; } private: bool m_barrier{}; }; template class checkpoint_holder final : public checkpoint_holder_base { public: explicit checkpoint_holder(bool barrier, std::future*& user_reference) : checkpoint_holder_base{barrier} { user_reference = &m_future; } ~checkpoint_holder() = default; checkpoint_holder(const checkpoint_holder&) = delete; checkpoint_holder& operator=(const checkpoint_holder&) = delete; checkpoint_holder(checkpoint_holder&&) = delete; checkpoint_holder& operator=(checkpoint_holder&&) = delete; void set_reset_value(std::size_t value) noexcept override { m_reset_value = value; } void reset() override { m_promise = std::promise{}; m_future = m_promise.get_future(); m_counter.store(m_reset_value, std::memory_order_release); } bool count_down() noexcept override { const bool last{--(m_counter) == 0}; if(last) { m_promise.set_value(); } return last; } bool check_barrier() const noexcept override { return m_counter.load(std::memory_order_acquire) == 1; } private: std::promise m_promise{}; std::future m_future{}; std::atomic m_counter{}; std::size_t m_reset_value{}; }; class checkpoint // checkpoints and barriers type { public: template checkpoint(bool barrier, std::future*& user_reference) : m_checkpoint{std::make_unique>(barrier, user_reference)} { } ~checkpoint() = default; checkpoint(const checkpoint&) = delete; checkpoint& operator=(const checkpoint&) = delete; checkpoint(checkpoint&&) = default; checkpoint& operator=(checkpoint&&) = default; void set_reset_value(std::size_t value) noexcept { m_checkpoint->set_reset_value(value); } void reset() { m_checkpoint->reset(); } bool count_down() noexcept { return m_checkpoint->count_down(); } bool check_barrier() const noexcept { return m_checkpoint->check_barrier(); } bool is_barrier() const noexcept { return m_checkpoint->is_barrier(); } checkpoint_holder_base* base() const noexcept { return m_checkpoint.get(); } private: std::unique_ptr m_checkpoint{}; }; using checkpoint_range = std::span; class task_holder_base // base class for type erasing in task { public: task_holder_base() = default; virtual ~task_holder_base() = default; task_holder_base(const task_holder_base&) = delete; task_holder_base& operator=(const task_holder_base&) = delete; task_holder_base(task_holder_base&&) = delete; task_holder_base& operator=(task_holder_base&&) = delete; virtual void reset() = 0; virtual void execute() = 0; // checkpoints to count down once this task is done void set_checkpoint_range(checkpoint_range checkpoints) { m_checkpoints = checkpoints; } protected: void trigger_checkpoints() { for(auto& checkpoint : m_checkpoints) { checkpoint->count_down(); } } private: checkpoint_range m_checkpoints{}; }; // task holder that not returns a value, used for execute function template class task_holder final : public task_holder_base { public: task_holder(Func&& func) : m_func{std::move(func)} { } ~task_holder() = default; task_holder(const task_holder&) = delete; task_holder& operator=(const task_holder&) = delete; task_holder(task_holder&&) = delete; task_holder& operator=(task_holder&&) = delete; void execute() override { m_func(); trigger_checkpoints(); } void reset() override { } private: Func m_func; }; // task holder that returns a value, used for invoke function template class return_task_holder final : public task_holder_base { public: return_task_holder(Func&& func, std::future*& user_reference) : m_func{std::move(func)} { user_reference = &m_future; } ~return_task_holder() = default; return_task_holder(const return_task_holder&) = delete; return_task_holder& operator=(const return_task_holder&) = delete; return_task_holder(return_task_holder&&) = delete; return_task_holder& operator=(return_task_holder&&) = delete; void execute() override { if constexpr(std::is_same_v) { m_func(); m_promise.set_value(); } else { m_promise.set_value(m_func()); } trigger_checkpoints(); } void reset() override { m_promise = std::promise{}; m_future = m_promise.get_future(); } private: Func m_func; std::promise m_promise{}; std::future m_future{}; }; class task // actual execution unit { public: template task(Func&& func) : m_holder{std::make_unique>(std::forward(func))} { } template task(Func&& func, std::future*& user_reference) : m_holder{std::make_unique>(std::forward(func), user_reference)} { } ~task() = default; task(const task&) = delete; task& operator=(const task&) = delete; task(task&&) = default; task& operator=(task&&) = default; void set_checkpoint_range(checkpoint_range checkpoints) { m_holder->set_checkpoint_range(checkpoints); } void execute() { m_holder->execute(); } void reset() { m_holder->reset(); } task_holder_base* holder() const noexcept { return m_holder.get(); } private: std::unique_ptr m_holder{}; }; class fence_holder { public: fence_holder() = default; ~fence_holder() = default; fence_holder(const fence_holder&) = delete; fence_holder& operator=(const fence_holder&) = delete; fence_holder(fence_holder&&) = delete; fence_holder& operator=(fence_holder&&) = delete; void set_condition(std::condition_variable& condition) noexcept { m_condition = &condition; } void reset() noexcept { m_signaled.store(false, std::memory_order_release); } void signal() noexcept { m_signaled.store(true, std::memory_order_release); m_condition->notify_one(); } bool is_signaled() const noexcept { return m_signaled.load(std::memory_order_acquire); } private: std::condition_variable* m_condition{}; std::atomic m_signaled{}; }; class fence { public: fence() : m_holder{std::make_unique()} { } ~fence() = default; fence(const fence&) = delete; fence& operator=(const fence&) = delete; fence(fence&&) = default; fence& operator=(fence&&) = default; void set_condition(std::condition_variable& condition) noexcept { m_holder->set_condition(condition); } void reset() noexcept { m_holder->reset(); } void signal() noexcept { m_holder->signal(); } bool is_signaled() const noexcept { return m_holder->is_signaled(); } fence_holder* holder() const noexcept { return m_holder.get(); } private: std::unique_ptr m_holder{}; }; using task_type = std::variant; } template class task_result { friend class task_builder; public: task_result() = default; ~task_result() = default; task_result(const task_result&) = delete; task_result& operator=(const task_result&) = delete; task_result(task_result&& other) noexcept : m_state{std::exchange(other.m_state, nullptr)} { } task_result& operator=(task_result&& other) noexcept { m_state = std::exchange(other.m_state, nullptr); return *this; } T get() { return m_state->get(); } bool valid() const noexcept { return m_state->valid(); } void wait() const { m_state->wait(); } template bool wait_for(const std::chrono::duration& timeout) const { return m_state->wait_for(timeout) == std::future_status::ready; } template bool wait_until(const std::chrono::duration& timeout) const { return m_state->wait_until(timeout) == std::future_status::ready; } private: std::future* m_state{}; }; using task_checkpoint = task_result; class task_fence { friend class task_builder; public: task_fence() = default; ~task_fence() = default; task_fence(const task_fence&) = delete; task_fence& operator=(const task_fence&) = delete; task_fence(task_fence&& other) noexcept : m_holder{std::exchange(other.m_holder, nullptr)} { } task_fence& operator=(task_fence&& other) noexcept { m_holder = std::exchange(other.m_holder, nullptr); return *this; } void signal() noexcept { m_holder->signal(); } private: impl::fence_holder* m_holder{}; }; class task_list { friend class thread_pool; friend class task_builder; public: constexpr task_list() = default; ~task_list() = default; task_list(const task_list&) = delete; task_list& operator=(const task_list&) = delete; task_list(task_list&&) = default; task_list& operator=(task_list&&) = default; private: void reset(std::condition_variable& condition) { for(auto& task : m_tasks) { std::visit([&condition](auto&& task) { using alternative_type = std::decay_t; if constexpr(std::is_same_v) { task.set_condition(condition); } task.reset(); }, task); } m_current = std::begin(m_tasks); } // bool: is the task_list done after that ? // count: number of task executable at call time // outputit: outputis to store the tasks template std::pair next(OutputIt output) { std::size_t count{}; while(m_current != std::end(m_tasks)) { if(std::holds_alternative(*m_current)) { auto& checkpoint{std::get(*m_current)}; if(checkpoint.is_barrier() && !checkpoint.check_barrier()) { return std::make_pair(false, count); } checkpoint.count_down(); } else if(std::holds_alternative(*m_current)) { auto& task{std::get(*m_current)}; *output++ = task.holder(); ++count; } else { auto& fence{std::get(*m_current)}; if(!fence.is_signaled()) { return std::make_pair(false, count); } } ++m_current; } return std::make_pair(true, count); } private: std::vector m_tasks{}; std::vector::iterator m_current{}; std::vector m_checkpoints{}; }; class task_builder { public: explicit task_builder(std::uint32_t thread_count = std::thread::hardware_concurrency()) : m_thread_count{thread_count != 0 ? thread_count : 8} { m_tasks.reserve(32); } ~task_builder() = default; task_builder(const task_builder&) = delete; task_builder& operator=(const task_builder&) = delete; task_builder(task_builder&&) = default; task_builder& operator=(task_builder&&) = default; template void execute(Func&& func, Args&&... args) { push_task([func = std::forward(func), ... args = std::forward(args)]() mutable { std::invoke(func, args...); }); } template [[nodiscard("Use execute instead of invoke when the returned future is not needed")]] auto invoke(Func&& func, Args&&... args) { using func_return_type = std::invoke_result_t; using result_type = task_result; result_type output{}; push_task([func = std::forward(func), ... args = std::forward(args)]() mutable { return std::invoke(func, args...); }, output.m_state); return output; } template void dispatch(std::uint32_t x, std::uint32_t y, std::uint32_t z, Func&& func, Args&&... args) { dispatch(x, y, z, std::numeric_limits::max(), std::forward(func), std::forward(args)...); } template void dispatch(std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint32_t max_invoke_per_task, Func&& func, Args&&... args) { assert(x != 0 && "nes::task_builder::dispatch called with x == 0"); assert(y != 0 && "nes::task_builder::dispatch called with y == 0"); assert(z != 0 && "nes::task_builder::dispatch called with z == 0"); assert(max_invoke_per_task != 0 && "nes::task_builder::dispatch called with max_invoke_per_task == 0"); const std::uint64_t total_calls{static_cast(x) * y * z}; if(total_calls < m_thread_count) // one invocation per thread { for(std::uint32_t current_z{}; current_z < z; ++current_z) { for(std::uint32_t current_y{}; current_y < y; ++current_y) { for(std::uint32_t current_x{}; current_x < x; ++current_x) { push_task([current_x, current_y, current_z, func = std::forward(func), ... args = std::forward(args)]() mutable { std::invoke(func, current_x, current_y, current_z, args...); }); } } } } else if(total_calls <= static_cast(max_invoke_per_task) * m_thread_count) // max_invoke_per_task or less invocations per thread { const std::uint64_t calls_per_thread{total_calls / m_thread_count}; std::uint64_t remainder{total_calls % m_thread_count}; std::uint64_t calls{}; while(calls < total_calls) { std::uint64_t count{calls_per_thread}; if(remainder > 0) { ++count; --remainder; } push_task([calls, count, x, y, z_factor = (x * y), func = std::forward(func), ... args = std::forward(args)]() mutable { for(std::uint64_t i{calls}; i < calls + count; ++i) { const auto current_x{static_cast(i % x)}; const auto current_y{static_cast((i / x) % y)}; const auto current_z{static_cast(i / z_factor)}; std::invoke(func, current_x, current_y, current_z, args...); } }); calls += count; } } else // create tasks that perform max_invoke_per_task invocations each, until we reach the total number of invocations { std::uint64_t calls{}; while(calls < total_calls) { std::uint64_t count{std::min(static_cast(max_invoke_per_task), total_calls - calls)}; push_task([calls, count, x, y, z_factor = (x * y), func = std::forward(func), ... args = std::forward(args)]() mutable { for(std::uint64_t i{calls}; i < calls + count; ++i) { const auto current_x{static_cast(i % x)}; const auto current_y{static_cast((i / x) % y)}; const auto current_z{static_cast(i / z_factor)}; std::invoke(func, current_x, current_y, current_z, args...); } }); calls += count; } } } task_checkpoint barrier() { task_result output{}; m_tasks.emplace_back(std::in_place_type, true, output.m_state); return output; } [[nodiscard]] task_checkpoint checkpoint() { task_result output{}; m_tasks.emplace_back(std::in_place_type, false, output.m_state); return output; } [[nodiscard]] task_fence fence() { task_fence output{}; output.m_holder = std::get(m_tasks.emplace_back(std::in_place_type)).holder(); return output; } task_list build() { barrier(); // this barrier is used by the pool to know when all the tasks are done and the list can be returned to user task_list output{}; output.m_tasks.reserve(std::size(m_tasks)); output.m_checkpoints.reserve(count_checkpoints()); auto begin{std::begin(m_tasks)}; auto current{begin}; std::size_t checkpoints_begin{}; std::size_t checkpoints_size{}; while(current != std::end(m_tasks)) { if(std::holds_alternative(*current)) { auto& checkpoint{std::get(*current)}; output.m_checkpoints.emplace_back(checkpoint.base()); ++checkpoints_size; if(checkpoint.is_barrier()) { const auto checkpoints_it{std::begin(output.m_checkpoints) + checkpoints_begin}; flush(output.m_tasks, checkpoints_it, checkpoints_it + checkpoints_size, begin, current + 1); checkpoints_begin += checkpoints_size; checkpoints_size = 0; begin = current + 1; } } ++current; } m_tasks.clear(); return output; } private: template void push_task(Args&&... args) { m_tasks.emplace_back(std::in_place_type, std::forward(args)...); } template void flush(std::vector& output, CheckpointIt checkpoints_begin, CheckpointIt checkpoints_end, InputIt begin, InputIt end) { std::size_t checkpoint_counter{}; while(begin != end) { if(std::holds_alternative(*begin)) { auto checkpoint{std::get(std::move(*begin))}; checkpoint.set_reset_value(checkpoint_counter + 1); //+ 1 for the caller output.emplace_back(std::in_place_type, std::move(checkpoint)); ++checkpoints_begin; } else if(std::holds_alternative(*begin)) { auto task{std::get(std::move(*begin))}; task.set_checkpoint_range(impl::checkpoint_range{checkpoints_begin, checkpoints_end}); output.emplace_back(std::in_place_type, std::move(task)); ++checkpoint_counter; } else { auto fence{std::get(std::move(*begin))}; output.emplace_back(std::in_place_type, std::move(fence)); } ++begin; } } std::size_t count_checkpoints() const noexcept { return std::count_if(std::begin(m_tasks), std::end(m_tasks), [](auto&& task) { return std::holds_alternative(task); }); } private: std::uint32_t m_thread_count{}; std::vector m_tasks{}; }; class thread_pool { public: explicit thread_pool(std::size_t thread_count = std::thread::hardware_concurrency()) { const auto worker_base = [this]() { worker_main(); }; thread_count = thread_count != 0 ? thread_count : 8; m_threads.reserve(thread_count); for(std::size_t i{}; i < thread_count; ++i) { m_threads.emplace_back(worker_base); } m_tasks.reserve(4 * thread_count); m_task_lists.reserve(4 * thread_count); } ~thread_pool() { std::unique_lock lock{m_mutex}; m_wait_condition.wait(lock, [this] { return std::empty(m_tasks) && std::empty(m_task_lists); }); m_running = false; lock.unlock(); m_worker_condition.notify_all(); for(auto&& thread : m_threads) { if(thread.joinable()) { thread.join(); } } } thread_pool(const thread_pool&) = delete; thread_pool& operator=(const thread_pool&) = delete; thread_pool(thread_pool&&) = delete; thread_pool& operator=(thread_pool&&) = delete; template void execute(Func&& func, Args&&... args) { push_impl([func = std::forward(func), ... args = std::forward(args)]() mutable { std::invoke(std::forward(func), std::forward(args)...); }); m_worker_condition.notify_one(); } template [[nodiscard("Use execute instead of invoke when the returned future is not needed")]] auto invoke(Func&& func, Args&&... args) { using return_type = std::invoke_result_t; using promise_type = std::promise; using future_type = std::future; promise_type promise{}; future_type future{promise.get_future()}; if constexpr(std::is_same_v) { push_impl([promise = std::move(promise), func = std::forward(func), ... args = std::forward(args)]() mutable { std::invoke(std::forward(func), std::forward(args)...); promise.set_value(); }); } else { push_impl([promise = std::move(promise), func = std::forward(func), ... args = std::forward(args)]() mutable { promise.set_value(std::invoke(std::forward(func), std::forward(args)...)); }); } m_worker_condition.notify_one(); return future; } std::future push(task_list&& list) { list.reset(m_worker_condition); std::unique_lock lock{m_mutex}; auto& data{m_task_lists.emplace_back()}; data.list = std::move(list); auto future{data.promise.get_future()}; const auto notify_count{update_task_lists()}; lock.unlock(); for(std::size_t i{}; i < std::min(notify_count, std::size(m_threads)); ++i) { m_worker_condition.notify_one(); } return future; } void wait_idle() { std::unique_lock lock{m_mutex}; m_wait_condition.wait(lock, [this] { return std::empty(m_tasks) && std::empty(m_task_lists); }); } std::size_t thread_count() const noexcept { return std::size(m_threads); } private: void worker_main() { while(true) { std::unique_lock lock{m_mutex}; m_worker_condition.wait(lock, [this] { if(std::empty(m_tasks) && !std::empty(m_task_lists)) { const auto notify_count{update_task_lists()}; for(std::size_t i{}; i < std::min(notify_count, std::size(m_threads)); ++i) { m_worker_condition.notify_one(); } } if(std::empty(m_tasks) && std::empty(m_task_lists)) { m_wait_condition.notify_all(); } return !m_running || !std::empty(m_tasks); }); if(!m_running) { break; } auto task{std::move(m_tasks.front())}; m_tasks.erase(std::begin(m_tasks)); lock.unlock(); if(std::holds_alternative(task)) { std::get(task).execute(); } else { std::get(task)->execute(); } } } template void push_impl(Func&& func) { std::lock_guard lock{m_mutex}; m_tasks.emplace_back(std::in_place_type, std::forward(func)); } std::size_t update_task_lists() { std::size_t notify_count{}; bool need_free{}; for(auto& list : m_task_lists) { const auto [end, count] = list.list.next(std::back_inserter(m_tasks)); if(end && count == 0) { list.promise.set_value(std::move(list.list)); list.need_free = true; need_free = true; } notify_count += count; } if(need_free) { const auto predicate = [](const task_list_data& list) { return list.need_free; }; m_task_lists.erase(std::remove_if(std::begin(m_task_lists), std::end(m_task_lists), predicate), std::end(m_task_lists)); } return notify_count; } private: struct task_list_data { task_list list{}; std::promise promise{}; bool need_free{}; }; private: std::vector m_threads{}; std::vector> m_tasks{}; std::vector m_task_lists{}; std::condition_variable m_worker_condition{}; std::condition_variable m_wait_condition{}; std::mutex m_mutex{}; bool m_running{true}; }; NES_INLINE_NAMESPACE_END } #endif ================================================ FILE: tests/common.hpp ================================================ /////////////////////////////////////////////////////////// /// Copyright 2019 Alexy Pellegrini /// /// Permission is hereby granted, free of charge, /// to any person obtaining a copy of this software /// and associated documentation files (the "Software"), /// to deal in the Software without restriction, /// including without limitation the rights to use, /// copy, modify, merge, publish, distribute, sublicense, /// and/or sell copies of the Software, and to permit /// persons to whom the Software is furnished to do so, /// subject to the following conditions: /// /// The above copyright notice and this permission notice /// shall be included in all copies or substantial portions /// of the Software. /// /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF /// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED /// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A /// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT /// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR /// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN /// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE /// OR OTHER DEALINGS IN THE SOFTWARE. /////////////////////////////////////////////////////////// #ifndef NOT_ENOUGH_STANDARDS_TESTS_COMMON #define NOT_ENOUGH_STANDARDS_TESTS_COMMON #include #include #include #define CHECK(expr, message) \ do \ { \ if(!(expr)) \ { \ std::cerr << "Error: Test failed at " << __FILE__ << " L." << __LINE__ << ":\n" \ << message << std::endl; \ std::exit(1); \ } \ } while(0) enum class data_type : std::uint32_t { uint32 = 1, float64, string }; inline std::string data_type_to_string(data_type type) { switch(type) { case data_type::uint32: return "uint32"; case data_type::float64: return "float64"; case data_type::string: return "string"; } return "Wrong type"; } #endif ================================================ FILE: tests/library.cpp ================================================ #include #ifdef _WIN32 #define NES_EXPORT __declspec(dllexport) #else #define NES_EXPORT #endif extern "C" NES_EXPORT std::int32_t nes_lib_func(); extern "C" NES_EXPORT std::int32_t nes_lib_func() { return 42; } ================================================ FILE: tests/process.cpp ================================================ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.hpp" #if defined(NES_WIN32_PROCESS) constexpr const char* other_path{"NotEnoughStandardsTestOther.exe"}; constexpr const char* lib_path{"NotEnoughStandardsTestLib.dll"}; #elif defined(NES_POSIX_PROCESS) constexpr const char* other_path{"./NotEnoughStandardsTestOther"}; constexpr const char* lib_path{"./NotEnoughStandardsTestLib.so"}; #endif static void shared_library_test() { nes::shared_library lib{lib_path}; auto func = lib.load("nes_lib_func"); CHECK(func, "Can not load library \"" << lib_path << "\""); const auto value{func()}; CHECK(value == 42, "Function returned wrong value " << value); } static void a_thread(nes::basic_pipe_istream& is) noexcept { data_type type{}; is.read(reinterpret_cast(&type), sizeof(data_type)); CHECK(type == data_type::uint32, "Wrong data type, expected uint32 got " << data_type_to_string(type)); std::uint32_t uint_value{}; is.read(reinterpret_cast(&uint_value), sizeof(std::uint32_t)); CHECK(uint_value == 42, "Wrong value, expected 42 got " << uint_value); is.read(reinterpret_cast(&type), sizeof(data_type)); CHECK(type == data_type::float64, "Wrong data type, expected float64 got " << data_type_to_string(type)); double float_value{}; is.read(reinterpret_cast(&float_value), sizeof(double)); CHECK(float_value > 3.139 && float_value < 3.141, "Wrong value, expected 3.14 got " << float_value); is.read(reinterpret_cast(&type), sizeof(data_type)); CHECK(type == data_type::string, "Wrong data type, expected string got " << data_type_to_string(type)); std::uint64_t str_size{}; is.read(reinterpret_cast(&str_size), sizeof(std::uint64_t)); std::string str_value{}; str_value.resize(str_size); is.read(std::data(str_value), static_cast(str_size)); is.read(reinterpret_cast(&type), sizeof(data_type)); CHECK(type == data_type::string, "Wrong data type, expected string got " << data_type_to_string(type)); is.read(reinterpret_cast(&str_size), sizeof(std::uint64_t)); str_value.clear(); is >> str_value; CHECK(str_value == "Hello", "Wrong value, expected \"Hello\" got \"" << str_value << "\""); str_value.clear(); str_value.resize(3); is.read(std::data(str_value), 3); CHECK(str_value == " wo", "Wrong value, expected \" wo\" got \"" << str_value << "\""); is >> str_value; CHECK(str_value == "rld!", "Wrong value, expected \"rld!\" got \"" << str_value << "\""); } static void pipe_test() { auto [is, os] = nes::make_anonymous_pipe(); std::thread thread{a_thread, std::ref(is)}; const data_type uint_type{data_type::uint32}; const std::uint32_t uint_value{42}; os.write(reinterpret_cast(&uint_type), sizeof(data_type)); os.write(reinterpret_cast(&uint_value), sizeof(std::uint32_t)); const data_type float_type{data_type::float64}; const double float_value{3.14}; os.write(reinterpret_cast(&float_type), sizeof(data_type)); os.write(reinterpret_cast(&float_value), sizeof(double)); const data_type str_type{data_type::string}; const std::string str_value{"Hello world!"}; const std::uint64_t size_size{std::size(str_value)}; os.write(reinterpret_cast(&str_type), sizeof(data_type)); os.write(reinterpret_cast(&size_size), sizeof(std::uint64_t)); os.write(std::data(str_value), static_cast(size_size)); // send it twice to test formatted input too os.write(reinterpret_cast(&str_type), sizeof(data_type)); os.write(reinterpret_cast(&size_size), sizeof(std::uint64_t)); os.put(str_value[0]); os.write(str_value.data() + 1, str_value.size() - 1); os.close(); if(thread.joinable()) { thread.join(); } } static void another_thread(const std::array& data, nes::semaphore& semaphore) { for(std::uint32_t i{}; i < 8; ++i) { semaphore.acquire(); CHECK(data[i] == i, "Wrong value expected " << i << " got " << data[i]); } } static void semaphore_test() { std::array data{0, 1}; nes::semaphore semaphore{2}; std::thread thread{another_thread, std::cref(data), std::ref(semaphore)}; for(std::uint32_t i{2}; i < 8; ++i) { data[i] = i; semaphore.release(); } if(thread.joinable()) { thread.join(); } } static void named_pipe_test() { nes::process other{other_path, std::vector{"named pipe"}, nes::process_options::grab_stdout}; nes::pipe_ostream os{"nes_test_pipe"}; CHECK(os, "Failed to open pipe."); const data_type uint_type{data_type::uint32}; const std::uint32_t uint_value{42}; os.write(reinterpret_cast(&uint_type), sizeof(data_type)); os.write(reinterpret_cast(&uint_value), sizeof(std::uint32_t)); const data_type float_type{data_type::float64}; const double float_value{3.14}; os.write(reinterpret_cast(&float_type), sizeof(data_type)); os.write(reinterpret_cast(&float_value), sizeof(double)); const data_type str_type{data_type::string}; const std::string str_value{"Hello world!"}; const std::uint64_t size_size{std::size(str_value)}; os.write(reinterpret_cast(&str_type), sizeof(data_type)); os.write(reinterpret_cast(&size_size), sizeof(std::uint64_t)); os.write(std::data(str_value), static_cast(size_size)); os.close(); CHECK(other.joinable(), "Process is not joinable"); other.join(); CHECK(other.return_code() == 0, "Other process failed with code " << other.return_code() << ":\n" << other.stdout_stream().rdbuf()); } static void process_test() { nes::process other{ other_path, {"Hey!", "\\\"12\"\"\\\\\\", "\\42\\", "It's \"me\"!"}, nes::process_options::grab_stdout }; std::cout << other.stdout_stream().rdbuf() << std::endl; CHECK(other.joinable(), "Process is not joinable"); other.join(); CHECK(other.return_code() == 0, "Other process failed with code " << other.return_code() << ":\n" << other.stdout_stream().rdbuf()); } static void process_kill_test() { nes::process other{other_path, std::vector{"process kill"}, nes::process_options::grab_stdout}; std::this_thread::sleep_for(std::chrono::milliseconds{200}); CHECK(other.kill(), "Failed to kill other process"); CHECK(!other.joinable(), "Other is still joinable"); } static void shared_memory_test() { nes::shared_memory memory{"nes_test_shared_memory", sizeof(std::uint64_t)}; auto value{memory.map(0)}; CHECK(value, "Failed to map shared memory"); *value = 42; CHECK(*value == 42, "Failed to write shared memory"); nes::process other{other_path, std::vector{"shared memory"}, nes::process_options::grab_stdout}; CHECK(other.joinable(), "Process is not joinable"); other.join(); CHECK(other.return_code() == 0, "Other process failed with code " << other.return_code() << ":\n" << other.stdout_stream().rdbuf()); CHECK(*value == 16777216, "Wrong value in shared memory, expected 16777216 got " << *value); other = nes::process{other_path, std::vector{"shared memory bad"}, nes::process_options::grab_stdout}; CHECK(other.joinable(), "Process is not joinable"); other.join(); CHECK(other.return_code() != 0, "Other process must return an error"); CHECK(*value == 16777216, "Wrong value in shared memory, expected 16777216 got " << *value); } static void named_mutex_test() { nes::named_mutex mutex{"nes_test_named_mutex"}; std::unique_lock lock{mutex}; nes::process other{other_path, std::vector{"named mutex"}, nes::process_options::grab_stdout}; lock.unlock(); CHECK(other.joinable(), "Process is not joinable"); other.join(); CHECK(other.return_code() == 0, "Other process failed with code " << other.return_code() << ":\n" << other.stdout_stream().rdbuf()); } static void timed_named_mutex_test() { nes::timed_named_mutex mutex{"nes_test_timed_named_mutex"}; std::unique_lock lock{mutex}; nes::process other{other_path, std::vector{"timed named mutex"}, nes::process_options::grab_stdout}; lock.unlock(); CHECK(other.joinable(), "Process is not joinable"); other.join(); CHECK(other.return_code() == 0, "Other process failed with code " << other.return_code() << ":\n" << other.stdout_stream().rdbuf()); } static void named_semaphore_test() { nes::named_semaphore semaphore{"nes_test_named_semaphore"}; nes::process other{other_path, std::vector{"named semaphore"}, nes::process_options::grab_stdout}; for(std::size_t i{}; i < 8; ++i) { semaphore.release(); } CHECK(other.joinable(), "Process is not joinable"); other.join(); CHECK(other.return_code() == 0, "Other process failed with code " << other.return_code() << ":\n" << other.stdout_stream().rdbuf()); } int main() { try { shared_library_test(); pipe_test(); semaphore_test(); process_test(); process_kill_test(); named_pipe_test(); shared_memory_test(); named_mutex_test(); timed_named_mutex_test(); named_semaphore_test(); } catch(const std::exception& e) { CHECK(false, e.what()); return 1; } } ================================================ FILE: tests/process_other.cpp ================================================ #include #include #include #include #include #include #include #include "common.hpp" [[noreturn]] static void to_infinity_and_beyond() { while(true) { std::this_thread::sleep_for(std::chrono::milliseconds{10}); } } static void named_pipe() { nes::pipe_istream is{"nes_test_pipe"}; CHECK(is, "Failed to open pipe."); data_type type{}; std::uint32_t uint_value{}; double float_value{}; std::string str_value{}; std::uint64_t str_size{}; is.read(reinterpret_cast(&type), sizeof(data_type)); CHECK(type == data_type::uint32, "Wrong data type, expected uint32 got " << data_type_to_string(type)); is.read(reinterpret_cast(&uint_value), sizeof(std::uint32_t)); CHECK(uint_value == 42, "Wrong value, expected 42 got " << uint_value); is.read(reinterpret_cast(&type), sizeof(data_type)); CHECK(type == data_type::float64, "Wrong data type, expected float64 got " << data_type_to_string(type)); is.read(reinterpret_cast(&float_value), sizeof(double)); CHECK(float_value > 3.139 && float_value < 3.141, "Wrong value, expected 3.14 got " << float_value); is.read(reinterpret_cast(&type), sizeof(data_type)); CHECK(type == data_type::string, "Wrong data type, expected string got " << data_type_to_string(type)); is.read(reinterpret_cast(&str_size), sizeof(std::uint64_t)); str_value.resize(str_size); is.read(std::data(str_value), static_cast(str_size)); CHECK(str_value == "Hello world!", "Wrong value, expected \"Hello world!\" got \"" << str_value << "\""); } static void shared_memory() { { nes::shared_memory memory{"nes_test_shared_memory", nes::shared_memory_options::constant}; const auto value{*memory.map(0)}; CHECK(value == 42, "Wrong value, expected 42 got " << value); } { nes::shared_memory new_memory{"nes_test_shared_memory"}; *new_memory.map(0) = 16777216; } } static void shared_memory_bad() { nes::shared_memory memory{"nes_test_shared_memory", nes::shared_memory_options::constant}; *memory.map(0) = 12; // theorically unreachable } static void named_mutex() { nes::named_mutex mutex{"nes_test_named_mutex"}; std::lock_guard lock{mutex}; // will throw in case of error } static void timed_named_mutex() { nes::timed_named_mutex mutex{"nes_test_timed_named_mutex"}; std::unique_lock lock{mutex, std::defer_lock}; while(!lock.try_lock_for(std::chrono::milliseconds{10})) ; // will throw in case of error } static void named_semaphore() { nes::named_semaphore semaphore{"nes_test_named_semaphore"}; for(std::size_t i{}; i < 8; ++i) { semaphore.acquire(); // will throw in case of error } } int main(int argc, char** argv) { for(int i{}; i < argc; ++i) { try { using namespace std::string_view_literals; if(argv[i] == "process kill"sv) { to_infinity_and_beyond(); } else if(argv[i] == "named pipe"sv) { named_pipe(); } else if(argv[i] == "shared memory"sv) { shared_memory(); } else if(argv[i] == "shared memory bad"sv) { shared_memory_bad(); } else if(argv[i] == "named mutex"sv) { named_mutex(); } else if(argv[i] == "timed named mutex"sv) { timed_named_mutex(); } else if(argv[i] == "named semaphore"sv) { named_semaphore(); } } catch(const std::exception& e) { std::cout << e.what() << std::endl; return 1; } } } ================================================ FILE: tests/thread_pool_test.cpp ================================================ #include #include #include "common.hpp" static void smoke_test() { static constexpr std::size_t buffer_size{8}; // Some buffers std::array input{32, 543, 4329, 12, 542, 656, 523, 98473}; std::array temp{}; std::array output{}; // The task builder nes::task_builder builder{}; builder.dispatch(buffer_size, 1, 1, [&input, &temp](std::uint32_t x, std::uint32_t y [[maybe_unused]], std::uint32_t z [[maybe_unused]] ) { temp[x] = input[x] * 2u; }); nes::task_checkpoint checkpoint{builder.checkpoint()}; nes::task_fence fence{builder.fence()}; builder.dispatch(buffer_size, 1, 1, [&input, &temp, &output](std::uint32_t x, std::uint32_t y [[maybe_unused]], std::uint32_t z [[maybe_unused]] ) { for(auto value : temp) { output[x] += (value + input[x]); } }); // Create a thread pool to run our task list. nes::thread_pool thread_pool{}; std::future future{thread_pool.push(builder.build())}; checkpoint.wait(); constexpr std::array temp_expected{64, 1086, 8658, 24, 1084, 1312, 1046, 196946}; CHECK(temp == temp_expected, "Wrong array values"); fence.signal(); future.wait(); constexpr std::array output_expected{210476, 214564, 244852, 210316, 214556, 215468, 214404, 998004}; CHECK(output == output_expected, "Wrong array values"); } int main() { try { smoke_test(); } catch(const std::exception& e) { CHECK(false, e.what()); return 1; } }