├── .gitignore ├── test ├── CMakeLists.txt ├── test_lr.cpp ├── test_cow.cpp ├── test_read_lock.cpp ├── test_deferred.cpp ├── test_lock.cpp ├── test_ordered.cpp ├── test_shared.cpp └── test_rcu.cpp ├── cmake ├── CsLibGuardedConfigVersion.cmake ├── CsLibGuardedConfig.cmake └── modules │ └── ParseAndAddCatchTests.cmake ├── LICENSE ├── src ├── cs_libguarded.cmake ├── cs_lock_guards.h ├── cs_ordered_guarded.h ├── cs_rcu_guarded.h ├── cs_shared_guarded.h ├── cs_lr_guarded.h ├── cs_plain_guarded.h ├── cs_deferred_guarded.h ├── cs_cow_guarded.h └── cs_rcu_list.h ├── README.md └── CMakeLists.txt /.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Threads REQUIRED) 2 | 3 | include(CTest) 4 | set(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS on) 5 | 6 | add_executable(CsLibGuardedTest "") 7 | 8 | target_link_libraries(CsLibGuardedTest 9 | PUBLIC 10 | CsLibGuarded 11 | Threads::Threads 12 | ) 13 | 14 | target_sources(CsLibGuardedTest 15 | PRIVATE 16 | ${CMAKE_CURRENT_SOURCE_DIR}/catch2/catch.hpp 17 | ${CMAKE_CURRENT_SOURCE_DIR}/test_cow.cpp 18 | ${CMAKE_CURRENT_SOURCE_DIR}/test_deferred.cpp 19 | ${CMAKE_CURRENT_SOURCE_DIR}/test_lock.cpp 20 | ${CMAKE_CURRENT_SOURCE_DIR}/test_read_lock.cpp 21 | ${CMAKE_CURRENT_SOURCE_DIR}/test_lr.cpp 22 | ${CMAKE_CURRENT_SOURCE_DIR}/test_ordered.cpp 23 | ${CMAKE_CURRENT_SOURCE_DIR}/test_rcu.cpp 24 | ${CMAKE_CURRENT_SOURCE_DIR}/test_shared.cpp 25 | ) 26 | 27 | include(ParseAndAddCatchTests) 28 | ParseAndAddCatchTests(CsLibGuardedTest) 29 | -------------------------------------------------------------------------------- /cmake/CsLibGuardedConfigVersion.cmake: -------------------------------------------------------------------------------- 1 | # *********************************************************************** 2 | # 3 | # Copyright (c) 2016-2025 Barbara Geller 4 | # Copyright (c) 2016-2025 Ansel Sermersheim 5 | # 6 | # This file is part of CsLibGuarded. 7 | # 8 | # CsLibGuarded is free software which is released under the BSD 2-Clause license. 9 | # For license details refer to the LICENSE provided with this project. 10 | # 11 | # CsLibGuarded is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | # 15 | # https://opensource.org/licenses/BSD-2-Clause 16 | # 17 | # *********************************************************************** 18 | 19 | set(CsLibGuarded_VERSION_MAJOR "@BUILD_MAJOR@" PARENT_SCOPE) 20 | set(CsLibGuarded_VERSION_MINOR "@BUILD_MINOR@" PARENT_SCOPE) 21 | set(CsLibGuarded_VERSION_PATCH "@BUILD_MICRO@" PARENT_SCOPE) 22 | 23 | set(CsLibGuarded_VERSION "@BUILD_MAJOR@.@BUILD_MINOR@.@BUILD_MICRO@" PARENT_SCOPE) 24 | set(CsLibGuarded_VERSION_API "@BUILD_MAJOR@.@BUILD_MINOR@" PARENT_SCOPE) 25 | 26 | set(PACKAGE_VERSION "@BUILD_MAJOR@.@BUILD_MINOR@.@BUILD_MICRO@") 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ** The following is the BSD2-clause license 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 18 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /src/cs_libguarded.cmake: -------------------------------------------------------------------------------- 1 | add_library(CsLibGuarded INTERFACE) 2 | add_library(CsLibGuarded::CsLibGuarded ALIAS CsLibGuarded) 3 | 4 | target_compile_features(CsLibGuarded 5 | INTERFACE 6 | cxx_std_20 7 | ) 8 | 9 | target_include_directories(CsLibGuarded 10 | INTERFACE 11 | $ 12 | $ 13 | $ 14 | ) 15 | 16 | set(CS_LIBGUARDED_INCLUDES 17 | ${CMAKE_CURRENT_SOURCE_DIR}/src/cs_cow_guarded.h 18 | ${CMAKE_CURRENT_SOURCE_DIR}/src/cs_deferred_guarded.h 19 | ${CMAKE_CURRENT_SOURCE_DIR}/src/cs_plain_guarded.h 20 | ${CMAKE_CURRENT_SOURCE_DIR}/src/cs_lock_guards.h 21 | ${CMAKE_CURRENT_SOURCE_DIR}/src/cs_lr_guarded.h 22 | ${CMAKE_CURRENT_SOURCE_DIR}/src/cs_ordered_guarded.h 23 | ${CMAKE_CURRENT_SOURCE_DIR}/src/cs_rcu_guarded.h 24 | ${CMAKE_CURRENT_SOURCE_DIR}/src/cs_rcu_list.h 25 | ${CMAKE_CURRENT_SOURCE_DIR}/src/cs_shared_guarded.h 26 | ) 27 | 28 | install( 29 | TARGETS CsLibGuarded 30 | EXPORT CsLibGuardedLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS} 31 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 32 | ) 33 | 34 | install( 35 | FILES ${CS_LIBGUARDED_INCLUDES} 36 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/CsLibGuarded 37 | COMPONENT CsLibGuarded 38 | ) 39 | 40 | install( 41 | EXPORT CsLibGuardedLibraryTargets 42 | NAMESPACE CsLibGuarded:: 43 | FILE CsLibGuardedLibraryTargets.cmake 44 | DESTINATION ${PKG_PREFIX} 45 | ) 46 | -------------------------------------------------------------------------------- /cmake/CsLibGuardedConfig.cmake: -------------------------------------------------------------------------------- 1 | # *********************************************************************** 2 | # 3 | # Copyright (c) 2016-2025 Barbara Geller 4 | # Copyright (c) 2016-2025 Ansel Sermersheim 5 | # 6 | # This file is part of CsLibGuarded. 7 | # 8 | # CsLibGuarded is free software which is released under the BSD 2-Clause license. 9 | # For license details refer to the LICENSE provided with this project. 10 | # 11 | # CsLibGuarded is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | # 15 | # https://opensource.org/licenses/BSD-2-Clause 16 | # 17 | # *********************************************************************** 18 | 19 | if(CsLibGuarded_FOUND) 20 | return() 21 | endif() 22 | 23 | set(CsLibGuarded_FOUND TRUE) 24 | 25 | # figure out install path 26 | get_filename_component(CsLibGuarded_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) 27 | get_filename_component(CsLibGuarded_PREFIX ${CsLibGuarded_CMAKE_DIR}/ ABSOLUTE) 28 | 29 | # library dependencies (contains definitions for imported targets) 30 | include("${CsLibGuarded_CMAKE_DIR}/CsLibGuardedLibraryTargets.cmake") 31 | 32 | # imported targets INCLUDE_DIRECTORIES 33 | get_target_property(CsLibGuarded_INCLUDES CsLibGuarded::CsLibGuarded INTERFACE_INCLUDE_DIRECTORIES) 34 | 35 | # export include base dir, imported in other projects 36 | set(CsLibGuarded_INCLUDE_DIR "@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_INCLUDEDIR@/CsLibGuarded") 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## CsLibGuarded 2 | 3 | ### Introduction 4 | 5 | The CsLibGuarded library is a standalone header only library for multithreaded programming. 6 | 7 | This library provides templated classes which prevent race conditions by controlling access to shared data. Existing 8 | multithreading primitives like mutexes and locks are only bound to the protected data by conventions. This makes it 9 | very easy to introduce bugs in your code by forgetting to use the right locks before accessing a block of data. The 10 | idea of this library is to tie the data and the locks in a type safe interface that only allows correct usage. 11 | 12 | 13 | ### System Requirements 14 | 15 | Building CsLibGuarded requires a C++20 compiler and a C++20 standard library. 16 | 17 | CMake build files are provided with the source distribution. The unit test binary executable is 18 | an optional part of the build process. 19 | 20 | This library has been tested with clang sanitizer and an extensive industry code review. 21 | 22 | 23 | ### Documentation 24 | 25 | Class level documentation for CsLibGuarded is available on the CopperSpice website: 26 | 27 | https://www.copperspice.com/docs/cs_libguarded/index.html 28 | 29 | 30 | ### Presentations 31 | 32 | Our YouTube channel contains over 75 videos about C++, programming fundamentals, Unicode/Strings, multithreading, 33 | graphics, CopperSpice, DoxyPress, and other software development topics. 34 | 35 | https://www.youtube.com/copperspice 36 | 37 | Links to additional videos can be found on our website. 38 | 39 | https://www.copperspice.com/presentations.html 40 | 41 | 42 | ### Authors / Contributors 43 | 44 | * **Ansel Sermersheim** 45 | * **Barbara Geller** 46 | * **Casey Bodley** 47 | * **Jan Wilmans** 48 | * **Eric Lemanissier** 49 | 50 | 51 | ### License 52 | 53 | This library is released under the BSD 2-clause license. For more information refer to the LICENSE file provided with 54 | this project. 55 | 56 | 57 | ### References 58 | 59 | * Website: https://www.copperspice.com 60 | * Twitter: https://twitter.com/copperspice_cpp 61 | * Email: info@copperspice.com 62 | 63 | 64 | * Github: https://github.com/copperspice 65 | 66 | 67 | * Forum: https://forum.copperspice.com 68 | * Journal: https://journal.copperspice.com 69 | 70 | -------------------------------------------------------------------------------- /test/test_lr.cpp: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Ansel Sermersheim 4 | * 5 | * This file is part of CsLibGuarded. 6 | * 7 | * CsLibGuarded is free software which is released under the BSD 2-Clause license. 8 | * For license details refer to the LICENSE provided with this project. 9 | * 10 | * CsLibGuarded is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | * 14 | * https://opensource.org/licenses/BSD-2-Clause 15 | * 16 | ***********************************************************************/ 17 | 18 | #include 19 | 20 | #include 21 | 22 | #include 23 | 24 | using namespace libguarded; 25 | 26 | TEST_CASE("LR traits", "[lr_guarded]") 27 | { 28 | using TestType = lr_guarded; 29 | 30 | REQUIRE(std::is_default_constructible_v == true); 31 | REQUIRE(std::is_constructible_v == true); 32 | REQUIRE(std::is_default_constructible_v == true); 33 | REQUIRE(std::is_move_constructible_v == true); 34 | REQUIRE(std::is_move_assignable_v == true); 35 | } 36 | 37 | TEST_CASE("LR guarded 1", "[lr_guarded]") 38 | { 39 | lr_guarded data(0); 40 | 41 | { 42 | data.modify([](int & x) { ++x; }); 43 | } 44 | 45 | { 46 | auto data_handle = data.lock_shared(); 47 | 48 | REQUIRE(data_handle != nullptr); 49 | REQUIRE(*data_handle == 1); 50 | 51 | std::thread th1([&data]() { 52 | auto data_handle2 = data.try_lock_shared(); 53 | REQUIRE(data_handle2 != nullptr); 54 | REQUIRE(*data_handle2 == 1); 55 | }); 56 | 57 | std::thread th2([&data]() { 58 | auto data_handle2 = data.try_lock_shared_for(std::chrono::milliseconds(20)); 59 | REQUIRE(data_handle2 != nullptr); 60 | REQUIRE(*data_handle2 == 1); 61 | 62 | }); 63 | 64 | std::thread th3([&data]() { 65 | auto data_handle2 = data.try_lock_shared_until(std::chrono::steady_clock::now() + 66 | std::chrono::milliseconds(20)); 67 | REQUIRE(data_handle2 != nullptr); 68 | REQUIRE(*data_handle2 == 1); 69 | }); 70 | 71 | th1.join(); 72 | 73 | th2.join(); 74 | th3.join(); 75 | } 76 | 77 | { 78 | auto data_handle = data.lock_shared(); 79 | 80 | REQUIRE(data_handle != nullptr); 81 | REQUIRE(*data_handle == 1); 82 | } 83 | } 84 | 85 | TEST_CASE("LR guarded 2", "[lr_guarded]") 86 | { 87 | lr_guarded data(0); 88 | 89 | std::thread th1([&data]() { 90 | for (int i = 0; i < 100000; ++i) { 91 | data.modify([](int & x) { ++x; }); 92 | } 93 | }); 94 | 95 | std::thread th2([&data]() { 96 | for (int i = 0; i < 100000; ++i) { 97 | data.modify([](int & x) { ++x; }); 98 | } 99 | }); 100 | 101 | std::thread th3([&data]() { 102 | int last_val = 0; 103 | while (last_val != 200000) { 104 | auto data_handle = data.lock_shared(); 105 | REQUIRE(last_val <= *data_handle); 106 | last_val = *data_handle; 107 | } 108 | }); 109 | 110 | th1.join(); 111 | th2.join(); 112 | th3.join(); 113 | 114 | auto data_handle = data.lock_shared(); 115 | 116 | REQUIRE(*data_handle == 200000); 117 | } 118 | -------------------------------------------------------------------------------- /test/test_cow.cpp: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Ansel Sermersheim 4 | * 5 | * This file is part of CsLibGuarded. 6 | * 7 | * CsLibGuarded is free software which is released under the BSD 2-Clause license. 8 | * For license details refer to the LICENSE provided with this project. 9 | * 10 | * CsLibGuarded is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | * 14 | * https://opensource.org/licenses/BSD-2-Clause 15 | * 16 | ***********************************************************************/ 17 | 18 | #include 19 | 20 | #include 21 | 22 | #include 23 | 24 | using namespace libguarded; 25 | 26 | TEST_CASE("Cow guarded 1", "[cow_guarded]") 27 | { 28 | cow_guarded data(0); 29 | 30 | { 31 | auto data_handle = data.lock(); 32 | 33 | ++(*data_handle); 34 | } 35 | 36 | { 37 | auto data_handle = data.lock_shared(); 38 | 39 | REQUIRE(data_handle != nullptr); 40 | REQUIRE(*data_handle == 1); 41 | 42 | std::thread th1([&data]() { 43 | auto data_handle2 = data.try_lock_shared(); 44 | REQUIRE(data_handle2 != nullptr); 45 | REQUIRE(*data_handle2 == 1); 46 | }); 47 | 48 | std::thread th2([&data]() { 49 | auto data_handle2 = data.try_lock_shared_for(std::chrono::milliseconds(20)); 50 | REQUIRE(data_handle2 != nullptr); 51 | REQUIRE(*data_handle2 == 1); 52 | 53 | }); 54 | 55 | std::thread th3([&data]() { 56 | auto data_handle2 = data.try_lock_shared_until(std::chrono::steady_clock::now() + 57 | std::chrono::milliseconds(20)); 58 | REQUIRE(data_handle2 != nullptr); 59 | REQUIRE(*data_handle2 == 1); 60 | }); 61 | 62 | th1.join(); 63 | 64 | th2.join(); 65 | th3.join(); 66 | } 67 | 68 | { 69 | auto data_handle = data.lock(); 70 | 71 | auto data_handle2 = data.lock_shared(); 72 | 73 | ++(*data_handle); 74 | REQUIRE(*data_handle == 2); 75 | 76 | REQUIRE(data_handle2 != nullptr); 77 | REQUIRE(*data_handle2 == 1); 78 | 79 | data_handle.cancel(); 80 | REQUIRE(data_handle == nullptr); 81 | 82 | REQUIRE(data_handle2 != nullptr); 83 | REQUIRE(*data_handle2 == 1); 84 | } 85 | 86 | { 87 | auto data_handle = data.lock_shared(); 88 | 89 | REQUIRE(data_handle != nullptr); 90 | REQUIRE(*data_handle == 1); 91 | } 92 | } 93 | 94 | TEST_CASE("Cow guarded 2", "[cow_guarded]") 95 | { 96 | cow_guarded data(0); 97 | 98 | std::thread th1([&data]() { 99 | for (int i = 0; i < 100000; ++i) { 100 | auto data_handle = data.lock(); 101 | ++(*data_handle); 102 | } 103 | }); 104 | 105 | std::thread th2([&data]() { 106 | for (int i = 0; i < 100000; ++i) { 107 | auto data_handle = data.lock(); 108 | ++(*data_handle); 109 | } 110 | }); 111 | 112 | std::thread th3([&data]() { 113 | int last_val = 0; 114 | for (int i = 0; i < 100000; ++i) { 115 | while (last_val != 200000) { 116 | auto data_handle = data.lock_shared(); 117 | REQUIRE(last_val <= *data_handle); 118 | last_val = *data_handle; 119 | } 120 | } 121 | }); 122 | 123 | th1.join(); 124 | th2.join(); 125 | th3.join(); 126 | 127 | auto data_handle = data.lock_shared(); 128 | 129 | REQUIRE(*data_handle == 200000); 130 | } 131 | -------------------------------------------------------------------------------- /test/test_read_lock.cpp: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Ansel Sermersheim 4 | * 5 | * This file is part of CsLibGuarded. 6 | * 7 | * CsLibGuarded is free software which is released under the BSD 2-Clause license. 8 | * For license details refer to the LICENSE provided with this project. 9 | * 10 | * CsLibGuarded is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | * 14 | * https://opensource.org/licenses/BSD-2-Clause 15 | * 16 | ***********************************************************************/ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | 28 | using namespace libguarded; 29 | 30 | TEMPLATE_TEST_CASE("read lock traits", "[read_lock]", 31 | shared_guarded, cow_guarded) 32 | { 33 | REQUIRE(std::is_default_constructible_v == true); 34 | REQUIRE(std::is_move_constructible_v == true); 35 | REQUIRE(std::is_move_assignable_v == true); 36 | } 37 | 38 | TEMPLATE_TEST_CASE("read lock basic", "[read_lock]", shared_guarded, cow_guarded) 39 | { 40 | SECTION("test multiple read lock") 41 | { 42 | TestType data(0); 43 | 44 | { 45 | auto data_handle = data.lock(); 46 | 47 | ++(*data_handle); 48 | } 49 | 50 | { 51 | std::thread th1([&data]() { auto data_handle = data.lock_shared(); }); 52 | std::thread th2([&data]() { auto data_handle = data.lock_shared(); }); 53 | 54 | th1.join(); 55 | th2.join(); 56 | } 57 | } 58 | 59 | SECTION("test multiple writers") 60 | { 61 | TestType data(0); 62 | 63 | std::thread th1([&data]() { 64 | for (int i = 0; i < 10000; ++i) { 65 | auto data_handle = data.lock(); 66 | ++(*data_handle); 67 | } 68 | }); 69 | 70 | std::thread th2([&data]() { 71 | for (int i = 0; i < 10000; ++i) { 72 | auto data_handle = data.lock(); 73 | ++(*data_handle); 74 | } 75 | }); 76 | 77 | th1.join(); 78 | th2.join(); 79 | 80 | REQUIRE(*(data.lock()) == 20000); 81 | } 82 | } 83 | 84 | TEMPLATE_TEST_CASE("read lock const", "[read_lock]", plain_guarded) 85 | { 86 | SECTION("test multiple read lock") 87 | { 88 | TestType data(0); 89 | 90 | { 91 | auto data_handle = data.lock(); 92 | 93 | ++(*data_handle); 94 | } 95 | 96 | { 97 | const auto& const_data = data; 98 | std::thread th1([&const_data]() { auto data_handle = const_data.lock(); }); 99 | std::thread th2([&const_data]() { auto data_handle = const_data.lock(); }); 100 | 101 | th1.join(); 102 | th2.join(); 103 | } 104 | } 105 | 106 | SECTION("test multiple writers") 107 | { 108 | TestType data(0); 109 | 110 | std::thread th1([&data]() { 111 | for (int i = 0; i < 10000; ++i) { 112 | auto data_handle = data.lock(); 113 | ++(*data_handle); 114 | } 115 | }); 116 | 117 | std::thread th2([&data]() { 118 | for (int i = 0; i < 10000; ++i) { 119 | auto data_handle = data.lock(); 120 | ++(*data_handle); 121 | } 122 | }); 123 | 124 | th1.join(); 125 | th2.join(); 126 | 127 | REQUIRE(*(data.lock()) == 20000); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /test/test_deferred.cpp: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Ansel Sermersheim 4 | * 5 | * This file is part of CsLibGuarded. 6 | * 7 | * CsLibGuarded is free software which is released under the BSD 2-Clause license. 8 | * For license details refer to the LICENSE provided with this project. 9 | * 10 | * CsLibGuarded is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | * 14 | * https://opensource.org/licenses/BSD-2-Clause 15 | * 16 | ***********************************************************************/ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | using shared_mutex = std::shared_timed_mutex; 23 | 24 | #include 25 | 26 | using namespace libguarded; 27 | 28 | TEST_CASE("Deferred guarded 1", "[deferred_guarded]") 29 | { 30 | deferred_guarded data(0); 31 | 32 | data.modify_detach([](int & x) { ++x; }); 33 | 34 | { 35 | auto data_handle = data.lock_shared(); 36 | 37 | REQUIRE(data_handle != nullptr); 38 | REQUIRE(*data_handle == 1); 39 | 40 | std::atomic th1_ok(true); 41 | std::atomic th2_ok(true); 42 | std::atomic th3_ok(true); 43 | 44 | std::thread th1([&data, &th1_ok]() { 45 | auto data_handle2 = data.try_lock_shared(); 46 | if (data_handle2 == nullptr) { 47 | th1_ok = false; 48 | } 49 | 50 | if (*data_handle2 != 1) { 51 | th1_ok = false; 52 | } 53 | }); 54 | 55 | std::thread th2([&data, &th2_ok]() { 56 | auto data_handle2 = data.try_lock_shared_for(std::chrono::milliseconds(20)); 57 | 58 | if (data_handle2 == nullptr) { 59 | th2_ok = false; 60 | } 61 | 62 | if (*data_handle2 != 1) { 63 | th2_ok = false; 64 | } 65 | }); 66 | 67 | std::thread th3([&data, &th3_ok]() { 68 | auto data_handle2 = data.try_lock_shared_until(std::chrono::steady_clock::now() + 69 | std::chrono::milliseconds(20)); 70 | if (data_handle2 == nullptr) { 71 | th3_ok = false; 72 | } 73 | 74 | if (*data_handle2 != 1) { 75 | th3_ok = false; 76 | } 77 | }); 78 | 79 | th1.join(); 80 | th2.join(); 81 | th3.join(); 82 | REQUIRE(th1_ok == true); 83 | REQUIRE(th2_ok == true); 84 | REQUIRE(th3_ok == true); 85 | } 86 | } 87 | 88 | TEST_CASE("Deferred guarded 2", "[deferred_guarded]") 89 | { 90 | deferred_guarded data(0); 91 | 92 | std::thread th1([&data]() { 93 | for (int i = 0; i < 100000; ++i) { 94 | data.modify_detach([](int & x) { ++x; }); 95 | } 96 | }); 97 | 98 | std::thread th2([&data]() { 99 | for (int i = 0; i < 100000; ++i) { 100 | auto fut = data.modify_async([](int & x) -> int { return ++x; }); 101 | fut.wait(); 102 | } 103 | }); 104 | 105 | std::thread th3([&data]() { 106 | for (int i = 0; i < 100000; ++i) { 107 | auto fut = data.modify_async([](int & x) -> void { ++x; }); 108 | fut.wait(); 109 | } 110 | }); 111 | 112 | std::thread th4([&data]() { 113 | int last_val = 0; 114 | while (last_val != 300000) { 115 | auto data_handle = data.lock_shared(); 116 | REQUIRE(last_val <= *data_handle); 117 | last_val = *data_handle; 118 | } 119 | }); 120 | 121 | th1.join(); 122 | th2.join(); 123 | th3.join(); 124 | th4.join(); 125 | 126 | auto data_handle = data.lock_shared(); 127 | 128 | REQUIRE(*data_handle == 300000); 129 | } 130 | -------------------------------------------------------------------------------- /test/test_lock.cpp: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Ansel Sermersheim 4 | * 5 | * This file is part of CsLibGuarded. 6 | * 7 | * CsLibGuarded is free software which is released under the BSD 2-Clause license. 8 | * For license details refer to the LICENSE provided with this project. 9 | * 10 | * CsLibGuarded is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | * 14 | * https://opensource.org/licenses/BSD-2-Clause 15 | * 16 | ***********************************************************************/ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #define CATCH_CONFIG_MAIN 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | using namespace libguarded; 31 | 32 | TEMPLATE_TEST_CASE("exclusive lock traits", "[exclusive_lock]", plain_guarded, 33 | shared_guarded, cow_guarded) 34 | { 35 | REQUIRE(std::is_default_constructible_v == true); 36 | REQUIRE(std::is_constructible_v == true); 37 | REQUIRE(std::is_default_constructible_v == true); 38 | REQUIRE(std::is_move_constructible_v == true); 39 | REQUIRE(std::is_move_assignable_v == true); 40 | } 41 | 42 | TEMPLATE_TEST_CASE("exclusive lock basic", "[exclusive_lock]", plain_guarded, 43 | shared_guarded, cow_guarded) 44 | { 45 | SECTION("initialize") 46 | { 47 | TestType data(1); 48 | REQUIRE(*(data.lock()) == 1); 49 | } 50 | 51 | SECTION("test lock and increment") 52 | { 53 | TestType data(0); 54 | 55 | { 56 | auto data_handle = data.lock(); 57 | 58 | ++(*data_handle); 59 | } 60 | } 61 | 62 | SECTION("test multiple writers") 63 | { 64 | TestType data(0); 65 | 66 | std::thread th1([&data]() { 67 | for (int i = 0; i < 10000; ++i) { 68 | auto data_handle = data.lock(); 69 | ++(*data_handle); 70 | } 71 | }); 72 | 73 | std::thread th2([&data]() { 74 | for (int i = 0; i < 10000; ++i) { 75 | auto data_handle = data.lock(); 76 | ++(*data_handle); 77 | } 78 | }); 79 | 80 | th1.join(); 81 | th2.join(); 82 | 83 | REQUIRE(*(data.lock()) == 20000); 84 | } 85 | } 86 | 87 | TEMPLATE_TEST_CASE("exclusive try_lock", "[exclusive_lock]", (plain_guarded), 88 | (shared_guarded), (cow_guarded)) 89 | { 90 | TestType data = 1; 91 | 92 | auto data_handle = data.try_lock(); 93 | 94 | std::atomic th1_ok(true); 95 | std::atomic th2_ok(true); 96 | std::atomic th3_ok(true); 97 | 98 | REQUIRE(data_handle != nullptr); 99 | REQUIRE(*data_handle == 1); 100 | 101 | // These tests must be done from another thread, because on 102 | // glibc std::mutex is actually a recursive mutex. 103 | 104 | std::thread th1([&data, &th1_ok]() { 105 | auto data_handle2 = data.try_lock(); 106 | if (data_handle2 != nullptr) { 107 | th1_ok = false; 108 | } 109 | }); 110 | 111 | std::thread th2([&data, &th2_ok]() { 112 | auto data_handle2 = data.try_lock_for(std::chrono::milliseconds(20)); 113 | if (data_handle2 != nullptr) { 114 | th2_ok = false; 115 | } 116 | }); 117 | 118 | std::thread th3([&data, &th3_ok]() { 119 | auto data_handle2 = data.try_lock_until(std::chrono::steady_clock::now() + std::chrono::milliseconds(20)); 120 | if (data_handle2 != nullptr) { 121 | th3_ok = false; 122 | } 123 | }); 124 | 125 | th1.join(); 126 | th2.join(); 127 | th3.join(); 128 | REQUIRE(th1_ok == true); 129 | REQUIRE(th2_ok == true); 130 | REQUIRE(th3_ok == true); 131 | } 132 | 133 | 134 | TEST_CASE("lock basic", "[lock]") 135 | { 136 | shared_guarded var1(5); 137 | plain_guarded var2(false); 138 | 139 | { 140 | auto [lock1, lock2] = lock_guards(var1, var2); 141 | 142 | REQUIRE(std::is_same_v, shared_guarded::handle> == true); 143 | REQUIRE(std::is_same_v, plain_guarded::handle> == true); 144 | 145 | REQUIRE(*lock1 == 5); 146 | REQUIRE(*lock2 == false); 147 | *lock1 = 10; 148 | *lock2 = true; 149 | } 150 | { 151 | auto [lock1, lock2] = lock_guards(as_reader(var1), var2); 152 | 153 | REQUIRE(std::is_same_v, shared_guarded::shared_handle> == true); 154 | REQUIRE(std::is_same_v, plain_guarded::handle> == true); 155 | 156 | REQUIRE(*lock1 == 10); 157 | REQUIRE(*lock2 == true); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /test/test_ordered.cpp: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Ansel Sermersheim 4 | * 5 | * This file is part of CsLibGuarded. 6 | * 7 | * CsLibGuarded is free software which is released under the BSD 2-Clause license. 8 | * For license details refer to the LICENSE provided with this project. 9 | * 10 | * CsLibGuarded is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | * 14 | * https://opensource.org/licenses/BSD-2-Clause 15 | * 16 | ***********************************************************************/ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | using shared_mutex = std::shared_timed_mutex; 25 | 26 | #include 27 | 28 | using namespace libguarded; 29 | 30 | TEST_CASE("Ordered traits", "[ordered_guarded]") 31 | { 32 | using TestType = ordered_guarded; 33 | 34 | REQUIRE(std::is_default_constructible_v == true); 35 | REQUIRE(std::is_constructible_v == true); 36 | REQUIRE(std::is_default_constructible_v == true); 37 | REQUIRE(std::is_move_constructible_v == true); 38 | REQUIRE(std::is_move_assignable_v == true); 39 | } 40 | 41 | 42 | TEST_CASE("Ordered guarded 1", "[ordered_guarded]") 43 | { 44 | ordered_guarded data(0); 45 | 46 | data.modify([](int &x) { ++x; }); 47 | 48 | { 49 | auto data_handle = data.lock_shared(); 50 | 51 | std::atomic th1_ok(true); 52 | std::atomic th2_ok(true); 53 | std::atomic th3_ok(true); 54 | 55 | REQUIRE(data_handle != nullptr); 56 | REQUIRE(*data_handle == 1); 57 | 58 | std::thread th1([&data, &th1_ok]() { 59 | auto data_handle2 = data.try_lock_shared(); 60 | if (data_handle2 == nullptr) { 61 | th1_ok = false; 62 | } 63 | 64 | if (*data_handle2 != 1) { 65 | th1_ok = false; 66 | } 67 | }); 68 | 69 | std::thread th2([&data, &th2_ok]() { 70 | auto data_handle2 = data.try_lock_shared_for(std::chrono::milliseconds(20)); 71 | 72 | if (data_handle2 == nullptr) { 73 | th2_ok = false; 74 | } 75 | 76 | if (*data_handle2 != 1) { 77 | th2_ok = false; 78 | } 79 | }); 80 | 81 | std::thread th3([&data, &th3_ok]() { 82 | auto data_handle2 = data.try_lock_shared_until(std::chrono::steady_clock::now() + 83 | std::chrono::milliseconds(20)); 84 | if (data_handle2 == nullptr) { 85 | th3_ok = false; 86 | } 87 | 88 | if (*data_handle2 != 1) { 89 | th3_ok = false; 90 | } 91 | }); 92 | 93 | th1.join(); 94 | th2.join(); 95 | th3.join(); 96 | REQUIRE(th1_ok == true); 97 | REQUIRE(th2_ok == true); 98 | REQUIRE(th3_ok == true); 99 | } 100 | } 101 | 102 | TEST_CASE("Ordered guarded 2", "[ordered_guarded]") 103 | { 104 | ordered_guarded data(0); 105 | 106 | std::atomic th1_ok(true); 107 | std::atomic th2_ok(true); 108 | std::atomic th3_ok(true); 109 | std::atomic th4_ok(true); 110 | 111 | std::thread th1([&data]() { 112 | for (int i = 0; i < 100000; ++i) { 113 | data.modify([](int &x) { ++x; }); 114 | } 115 | }); 116 | 117 | std::thread th2([&data, &th2_ok]() { 118 | for (int i = 0; i < 100000; ++i) { 119 | int check_i = data.modify([i](int &x) { 120 | ++x; 121 | return i; 122 | }); 123 | 124 | if (check_i != i) { 125 | th2_ok = false; 126 | } 127 | } 128 | }); 129 | 130 | std::thread th3([&data, &th3_ok]() { 131 | int last_val = 0; 132 | while (last_val != 200000) { 133 | auto data_handle = data.lock_shared(); 134 | 135 | if (last_val > *data_handle) { 136 | th3_ok = false; 137 | } 138 | 139 | last_val = *data_handle; 140 | } 141 | }); 142 | 143 | std::thread th4([&data, &th4_ok]() { 144 | int last_val = 0; 145 | 146 | while (last_val != 200000) { 147 | int new_data = data.read([](const int &x) { return x; }); 148 | 149 | if (last_val > new_data) { 150 | th4_ok = false; 151 | } 152 | 153 | last_val = new_data; 154 | } 155 | }); 156 | 157 | th1.join(); 158 | th2.join(); 159 | 160 | { 161 | auto data_handle = data.lock_shared(); 162 | 163 | REQUIRE(*data_handle == 200000); 164 | } 165 | 166 | th3.join(); 167 | th4.join(); 168 | 169 | REQUIRE(th1_ok == true); 170 | REQUIRE(th2_ok == true); 171 | REQUIRE(th3_ok == true); 172 | REQUIRE(th4_ok == true); 173 | 174 | REQUIRE(data.modify([](const int &x) { return x; }) == 200000); 175 | } 176 | -------------------------------------------------------------------------------- /src/cs_lock_guards.h: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Ansel Sermersheim 4 | * 5 | * This file is part of CsLibGuarded. 6 | * 7 | * CsLibGuarded is free software which is released under the BSD 2-Clause license. 8 | * For license details refer to the LICENSE provided with this project. 9 | * 10 | * CsLibGuarded is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | * 14 | * https://opensource.org/licenses/BSD-2-Clause 15 | * 16 | ***********************************************************************/ 17 | 18 | #ifndef CSLIBGUARDED_LOCK_GUARDS_H 19 | #define CSLIBGUARDED_LOCK_GUARDS_H 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | namespace libguarded 27 | { 28 | 29 | namespace detail 30 | { 31 | 32 | // type trait to get the address of a guard, specialization below 33 | template 34 | class guard_address 35 | { 36 | public: 37 | constexpr guard_address(const T &object) : value(&object){}; 38 | 39 | const void *const value; 40 | }; 41 | 42 | // Base class to sort by address and virtually invoke the actual try_lock method 43 | class guard_locker_base 44 | { 45 | public: 46 | guard_locker_base(const void *address) : m_address(address){}; 47 | 48 | virtual ~guard_locker_base() = default; 49 | 50 | [[nodiscard]] virtual bool do_try_lock() = 0; 51 | virtual void reset() = 0; 52 | 53 | [[nodiscard]] bool operator<(const guard_locker_base &rhs) const noexcept 54 | { 55 | return m_address < rhs.m_address; 56 | } 57 | 58 | private: 59 | const void *const m_address; 60 | }; 61 | 62 | // Concrete implementation, stores a reference to a guarded object and a handle from calling 63 | // the underlying try_lock() method 64 | template 65 | class guard_locker : public guard_locker_base 66 | { 67 | public: 68 | using lock_type = std::decay_t().try_lock())>; 69 | 70 | guard_locker(T &guard) : guard_locker_base(guard_address(guard).value), m_guard(guard) 71 | { 72 | } 73 | 74 | [[nodiscard]] bool do_try_lock() override 75 | { 76 | m_lock = m_guard.try_lock(); 77 | return m_lock != nullptr; 78 | } 79 | 80 | void reset() override 81 | { 82 | m_lock.reset(); 83 | } 84 | 85 | [[nodiscard]] lock_type take_lock() && 86 | { 87 | return std::move(m_lock); 88 | } 89 | 90 | private: 91 | lock_type m_lock; 92 | T &m_guard; 93 | }; 94 | 95 | // Adapter class which forwards the try_lock() method to the underlying type's try_lock_shared() 96 | // method so we can get a read lock 97 | template 98 | class guard_reader 99 | { 100 | public: 101 | guard_reader(T &guard) : m_guard(guard) 102 | { 103 | } 104 | 105 | [[nodiscard]] auto try_lock() 106 | { 107 | return m_guard.try_lock_shared(); 108 | } 109 | 110 | private: 111 | T &m_guard; 112 | 113 | friend class guard_address>; 114 | }; 115 | 116 | // Specialization for guard_address which returns the address of the underlying guard instead of the 117 | // adapter object 118 | template 119 | class guard_address> 120 | { 121 | public: 122 | constexpr guard_address(const guard_reader &object) : value(&(object.m_guard)){}; 123 | 124 | const void *const value; 125 | }; 126 | 127 | } // namespace detail 128 | 129 | template 130 | [[nodiscard]] auto as_reader(T &guard) 131 | { 132 | return detail::guard_reader(guard); 133 | } 134 | 135 | template 136 | [[nodiscard]] auto lock_guards(Ts &&...Vs) 137 | { 138 | using namespace libguarded::detail; 139 | 140 | std::tuple...> lockers = {guard_locker(Vs)...}; 141 | 142 | auto lock_sequence = std::apply( 143 | [](auto &&...args) { 144 | std::array retval = {(&args)...}; 145 | return retval; 146 | }, 147 | lockers); 148 | 149 | // sort the lock sequence based on the address of the underlying guarded object 150 | std::sort(lock_sequence.begin(), lock_sequence.end()); 151 | 152 | auto iter = lock_sequence.begin(); 153 | 154 | // walk through all locks from the beginning 155 | while (iter != lock_sequence.end()) { 156 | if ((*iter)->do_try_lock()) { 157 | // this lock succeeded, go on 158 | ++iter; 159 | } else { 160 | // a lock failed, release as we walk back to the beginning 161 | while (iter != lock_sequence.begin()) { 162 | --iter; 163 | (*iter)->reset(); 164 | } 165 | } 166 | } 167 | 168 | // all locks succeeded 169 | 170 | return std::apply( 171 | [](auto &&...args) { 172 | // move locks out of the guard_locker objects 173 | return std::make_tuple(std::move(args).take_lock()...); 174 | }, 175 | std::move(lockers)); 176 | } 177 | 178 | } // namespace libguarded 179 | 180 | #endif 181 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.18.0 FATAL_ERROR) 2 | 3 | cmake_policy(VERSION 3.18.0..3.29.6) 4 | 5 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.19.0") 6 | # allows spaces in ctest names 7 | cmake_policy(SET CMP0110 NEW) 8 | endif() 9 | 10 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.20.0") 11 | # enable RTTI on MSVC 12 | cmake_policy(SET CMP0117 OLD) 13 | endif() 14 | 15 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules/") 16 | 17 | project(cs_libguarded) 18 | 19 | set(BUILD_MAJOR "2") 20 | set(BUILD_MINOR "0") 21 | set(BUILD_MICRO "0") 22 | 23 | set(BUILD_COMPONENTS "cs_libguarded") 24 | 25 | # catch2 set up 26 | option(BUILD_TESTS "Enables building the Catch2 unit tests" OFF) 27 | 28 | include(CheckCXXCompilerFlag) 29 | include(CheckCXXSourceCompiles) 30 | include(CheckIncludeFile) 31 | include(CheckIncludeFiles) 32 | include(CheckTypeSize) 33 | 34 | # location for install or package 35 | if (CMAKE_SYSTEM_NAME MATCHES "Darwin") 36 | include(GNUInstallDirs) 37 | set(CMAKE_INSTALL_RPATH "@executable_path") 38 | 39 | elseif (CMAKE_SYSTEM_NAME MATCHES "(Linux|OpenBSD|FreeBSD|NetBSD|DragonFly)") 40 | include(GNUInstallDirs) 41 | set(CMAKE_INSTALL_RPATH "\$ORIGIN") 42 | 43 | elseif (CMAKE_SYSTEM_NAME MATCHES "Windows") 44 | set(CMAKE_INSTALL_BINDIR bin) 45 | set(CMAKE_INSTALL_LIBDIR lib) 46 | set(CMAKE_INSTALL_INCLUDEDIR include) 47 | 48 | endif() 49 | 50 | set(PACKAGE "cs_libguarded") 51 | set(PACKAGE_NAME "CsLibGuarded") 52 | set(PACKAGE_VERSION "${BUILD_MAJOR}.${BUILD_MINOR}.${BUILD_MICRO}") 53 | set(PACKAGE_STRING "cs_libguarded ${BUILD_MAJOR}.${BUILD_MINOR}.${BUILD_MICRO}") 54 | set(PACKAGE_TARNAME "cs_libguarded") 55 | set(PACKAGE_BUGREPORT "info@copperspice.com") 56 | set(PACKAGE_URL "https://www.copperspice.com/") 57 | 58 | set(CPACK_PACKAGE_NAME ${PROJECT_NAME} ) 59 | set(CPACK_PACKAGE_VENDOR "CopperSpice") 60 | set(CPACK_PACKAGE_CONTACT "info@copperspice.com") 61 | 62 | set(CPACK_PACKAGE_VERSION_MAJOR ${BUILD_MAJOR}) 63 | set(CPACK_PACKAGE_VERSION_MINOR ${BUILD_MINOR}) 64 | set(CPACK_PACKAGE_VERSION_PATCH ${BUILD_MICRO}) 65 | 66 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A library for managing shared data") 67 | set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/README.md") 68 | 69 | set(CPACK_SOURCE_IGNORE_FILES "/build/;/.git;${CPACK_SOURCE_IGNORE_FILES}") 70 | set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CMAKE_INSTALL_PREFIX}) 71 | set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF) 72 | 73 | include(CPack) 74 | 75 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 76 | set(CMAKE_INCLUDE_DIRECTORIES_BEFORE ON) 77 | set(CMAKE_CXX_EXTENSIONS OFF) 78 | 79 | if(CMAKE_SYSTEM_NAME MATCHES "Darwin") 80 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-undefined,error") 81 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-undefined,error") 82 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-undefined,error") 83 | elseif(CMAKE_SYSTEM_NAME MATCHES "(OpenBSD|FreeBSD|NetBSD|DragonFly)") 84 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--no-undefined") 85 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ") 86 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined") 87 | 88 | elseif(MSVC) 89 | string (REGEX REPLACE "/W3" "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}" ) 90 | string (REGEX REPLACE "/W3" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 91 | 92 | add_compile_options("/utf-8") 93 | else() 94 | # Linux, Windows (MinGW) 95 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--no-undefined") 96 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined") 97 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined") 98 | endif() 99 | 100 | # destination for cmake export files 101 | if (CMAKE_SYSTEM_NAME MATCHES "Windows") 102 | set(PKG_PREFIX "cmake/CsLibGuarded") 103 | 104 | else() 105 | set(PKG_PREFIX "${CMAKE_INSTALL_LIBDIR}/cmake/CsLibGuarded") 106 | 107 | endif() 108 | 109 | # catch2 set up 110 | if(BUILD_TESTS) 111 | enable_testing() 112 | add_subdirectory(test) 113 | endif() 114 | 115 | configure_file( 116 | ${CMAKE_SOURCE_DIR}/cmake/CsLibGuardedConfig.cmake 117 | ${CMAKE_BINARY_DIR}/CsLibGuardedConfig.cmake 118 | @ONLY 119 | ) 120 | 121 | configure_file( 122 | ${CMAKE_SOURCE_DIR}/cmake/CsLibGuardedConfigVersion.cmake 123 | ${CMAKE_BINARY_DIR}/CsLibGuardedConfigVersion.cmake 124 | @ONLY 125 | ) 126 | 127 | install( 128 | FILES 129 | ${CMAKE_BINARY_DIR}/CsLibGuardedConfig.cmake 130 | ${CMAKE_BINARY_DIR}/CsLibGuardedConfigVersion.cmake 131 | DESTINATION ${PKG_PREFIX} 132 | ) 133 | 134 | # file locations for building 135 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 136 | 137 | include(src/cs_libguarded.cmake) 138 | 139 | if (${CMAKE_SIZEOF_VOID_P} EQUAL 4) 140 | set(TARGETBITS 32) 141 | else() 142 | set(TARGETBITS 64) 143 | endif() 144 | 145 | message("") 146 | message("CsLibGuarded configured to run on: ${CMAKE_SYSTEM_NAME} ${TARGETBITS} bit, ${CMAKE_BUILD_TYPE} Mode") 147 | message("CsLibGuarded will be built in: ${CMAKE_BINARY_DIR}") 148 | message("CsLibGuarded will be installed in: ${CMAKE_INSTALL_PREFIX}") 149 | message("\n") 150 | -------------------------------------------------------------------------------- /src/cs_ordered_guarded.h: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Ansel Sermersheim 4 | * 5 | * This file is part of CsLibGuarded. 6 | * 7 | * CsLibGuarded is free software which is released under the BSD 2-Clause license. 8 | * For license details refer to the LICENSE provided with this project. 9 | * 10 | * CsLibGuarded is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | * 14 | * https://opensource.org/licenses/BSD-2-Clause 15 | * 16 | ***********************************************************************/ 17 | 18 | #ifndef CSLIBGUARDED_ORDERED_GUARDED_H 19 | #define CSLIBGUARDED_ORDERED_GUARDED_H 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | namespace libguarded 27 | { 28 | 29 | /** 30 | \headerfile cs_ordered_guarded.h 31 | 32 | This templated class wraps an object. The protected object may be 33 | read by any number of threads simultaneously, but only one thread 34 | may modify the object at a time. 35 | 36 | This class will use std::shared_timed_mutex for the internal 37 | locking mechanism by default. In C++17, the class std::shared_mutex 38 | is available as well. 39 | 40 | The handle returned by the various lock methods is moveable but not 41 | copyable. 42 | */ 43 | template 44 | class ordered_guarded 45 | { 46 | private: 47 | class shared_deleter; 48 | 49 | public: 50 | using shared_handle = std::unique_ptr; 51 | 52 | /** 53 | Construct a guarded object. This constructor will accept any number 54 | of parameters, all of which are forwarded to the constructor of T. 55 | */ 56 | template 57 | ordered_guarded(Us &&... data); 58 | 59 | template 60 | decltype(auto) modify(Func &&func); 61 | 62 | template 63 | [[nodiscard]] decltype(auto) read(Func &&func) const; 64 | 65 | [[nodiscard]] shared_handle lock_shared() const; 66 | [[nodiscard]] shared_handle try_lock_shared() const; 67 | 68 | template 69 | [[nodiscard]] shared_handle try_lock_shared_for(const Duration &duration) const; 70 | 71 | template 72 | [[nodiscard]] shared_handle try_lock_shared_until(const TimePoint &timepoint) const; 73 | 74 | private: 75 | class shared_deleter 76 | { 77 | public: 78 | using pointer = const T *; 79 | 80 | shared_deleter() : m_deleter_mutex(nullptr) {} 81 | 82 | shared_deleter(M &mutex) 83 | : m_deleter_mutex(&mutex) 84 | { 85 | } 86 | 87 | void operator()(const T *ptr) { 88 | if (ptr && m_deleter_mutex) { 89 | m_deleter_mutex->unlock_shared(); 90 | } 91 | } 92 | 93 | private: 94 | M *m_deleter_mutex; 95 | }; 96 | 97 | T m_obj; 98 | mutable M m_mutex; 99 | }; 100 | 101 | template 102 | template 103 | ordered_guarded::ordered_guarded(Us &&... data) 104 | : m_obj(std::forward(data)...) 105 | { 106 | } 107 | 108 | template 109 | template 110 | decltype(auto) ordered_guarded::modify(Func &&func) 111 | { 112 | std::lock_guard lock(m_mutex); 113 | 114 | return func(m_obj); 115 | } 116 | 117 | template 118 | template 119 | decltype(auto) ordered_guarded::read(Func &&func) const 120 | { 121 | std::shared_lock lock(m_mutex); 122 | 123 | return func(m_obj); 124 | } 125 | 126 | template 127 | auto ordered_guarded::lock_shared() const -> shared_handle 128 | { 129 | m_mutex.lock_shared(); 130 | return std::unique_ptr(&m_obj, shared_deleter(m_mutex)); 131 | } 132 | 133 | template 134 | auto ordered_guarded::try_lock_shared() const -> shared_handle 135 | { 136 | if (m_mutex.try_lock_shared()) { 137 | return std::unique_ptr(&m_obj, shared_deleter(m_mutex)); 138 | } else { 139 | return std::unique_ptr(nullptr, shared_deleter(m_mutex)); 140 | } 141 | } 142 | 143 | template 144 | template 145 | auto ordered_guarded::try_lock_shared_for(const Duration &duration) const -> shared_handle 146 | { 147 | if (m_mutex.try_lock_shared_for(duration)) { 148 | return std::unique_ptr(&m_obj, shared_deleter(m_mutex)); 149 | } else { 150 | return std::unique_ptr(nullptr, shared_deleter(m_mutex)); 151 | } 152 | } 153 | 154 | template 155 | template 156 | auto ordered_guarded::try_lock_shared_until(const TimePoint &timepoint) const -> shared_handle 157 | { 158 | if (m_mutex.try_lock_shared_until(timepoint)) { 159 | return std::unique_ptr(&m_obj, shared_deleter(m_mutex)); 160 | } else { 161 | return std::unique_ptr(nullptr, shared_deleter(m_mutex)); 162 | } 163 | } 164 | 165 | } // namespace libguarded 166 | 167 | #endif 168 | -------------------------------------------------------------------------------- /src/cs_rcu_guarded.h: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Ansel Sermersheim 4 | * 5 | * This file is part of CsLibGuarded. 6 | * 7 | * CsLibGuarded is free software which is released under the BSD 2-Clause license. 8 | * For license details refer to the LICENSE provided with this project. 9 | * 10 | * CsLibGuarded is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | * 14 | * https://opensource.org/licenses/BSD-2-Clause 15 | * 16 | ***********************************************************************/ 17 | 18 | #ifndef CSLIBGUARDED_RCU_GUARDED_H 19 | #define CSLIBGUARDED_RCU_GUARDED_H 20 | 21 | #include 22 | 23 | namespace libguarded 24 | { 25 | 26 | /** 27 | \headerfile cs_rcu_guarded.h 28 | 29 | This templated class implements a mechanism which controls access 30 | to an RCU data structure. The only way to access the underlying 31 | data structure is to use either the lock_read or lock_write methods 32 | to receive a read-only or writable handle to the data structure, 33 | respectively. 34 | */ 35 | template 36 | class rcu_guarded 37 | { 38 | public: 39 | class write_handle; 40 | class read_handle; 41 | 42 | template 43 | rcu_guarded(Us &&... data); 44 | 45 | // write access 46 | [[nodiscard]] write_handle lock_write(); 47 | 48 | // read access 49 | [[nodiscard]] read_handle lock_read() const; 50 | 51 | class write_handle 52 | { 53 | public: 54 | using pointer = T *; 55 | using element_type = T; 56 | 57 | write_handle(T *ptr); 58 | 59 | write_handle(const write_handle &other) = delete; 60 | write_handle &operator=(const write_handle &other) = delete; 61 | 62 | write_handle(write_handle &&other) { 63 | m_ptr = other.m_ptr; 64 | m_guard = std::move(other.m_guard); 65 | m_accessed = other.m_accessed; 66 | 67 | other.m_ptr = nullptr; 68 | other.m_accessed = false; 69 | } 70 | 71 | write_handle &operator=(write_handle &&other) { 72 | 73 | if (m_accessed) { 74 | m_guard.rcu_write_unlock(*m_ptr); 75 | } 76 | 77 | m_ptr = other.m_ptr; 78 | m_guard = std::move(other.m_guard); 79 | m_accessed = other.m_accessed; 80 | 81 | other.m_ptr = nullptr; 82 | other.m_accessed = false; 83 | } 84 | 85 | ~write_handle() 86 | { 87 | if (m_accessed) { 88 | m_guard.rcu_write_unlock(*m_ptr); 89 | } 90 | } 91 | 92 | T &operator*() const { 93 | access(); 94 | return *m_ptr; 95 | } 96 | 97 | T *operator->() const { 98 | access(); 99 | return m_ptr; 100 | } 101 | 102 | private: 103 | void access() const { 104 | if (! m_accessed) { 105 | m_guard.rcu_write_lock(*m_ptr); 106 | m_accessed = true; 107 | } 108 | } 109 | 110 | T *m_ptr; 111 | mutable typename T::rcu_write_guard m_guard; 112 | mutable bool m_accessed; 113 | }; 114 | 115 | class read_handle 116 | { 117 | public: 118 | using pointer = const T *; 119 | using element_type = const T; 120 | 121 | read_handle(const T *ptr) 122 | : m_ptr(ptr), m_accessed(false) 123 | { 124 | } 125 | 126 | read_handle(const read_handle &other) = delete; 127 | read_handle &operator=(const read_handle &other) = delete; 128 | 129 | read_handle(read_handle &&other) { 130 | m_ptr = other.m_ptr; 131 | m_guard = std::move(other.m_guard); 132 | m_accessed = other.m_accessed; 133 | 134 | other.m_ptr = nullptr; 135 | other.m_accessed = false; 136 | } 137 | 138 | read_handle &operator=(read_handle &&other) { 139 | 140 | if (m_accessed) { 141 | m_guard.rcu_read_unlock(*m_ptr); 142 | } 143 | 144 | m_ptr = other.m_ptr; 145 | m_guard = std::move(other.m_guard); 146 | m_accessed = other.m_accessed; 147 | 148 | other.m_ptr = nullptr; 149 | other.m_accessed = false; 150 | } 151 | 152 | ~read_handle() 153 | { 154 | if (m_accessed) { 155 | m_guard.rcu_read_unlock(*m_ptr); 156 | } 157 | } 158 | 159 | const T &operator*() const { 160 | access(); 161 | return *m_ptr; 162 | } 163 | 164 | const T *operator->() const { 165 | access(); 166 | return m_ptr; 167 | } 168 | 169 | private: 170 | void access() const { 171 | if (! m_accessed) { 172 | m_guard.rcu_read_lock(*m_ptr); 173 | m_accessed = true; 174 | } 175 | } 176 | 177 | const T *m_ptr; 178 | mutable typename T::rcu_read_guard m_guard; 179 | mutable bool m_accessed; 180 | }; 181 | 182 | private: 183 | T m_obj; 184 | }; 185 | 186 | template 187 | template 188 | rcu_guarded::rcu_guarded(Us &&... data) 189 | : m_obj(std::forward(data)...) 190 | { 191 | } 192 | 193 | template 194 | auto rcu_guarded::lock_write() -> write_handle 195 | { 196 | return write_handle(&m_obj); 197 | } 198 | 199 | template 200 | auto rcu_guarded::lock_read() const -> read_handle 201 | { 202 | return read_handle(&m_obj); 203 | } 204 | 205 | template 206 | rcu_guarded::write_handle::write_handle(T *ptr) 207 | : m_ptr(ptr), m_accessed(false) 208 | { 209 | } 210 | 211 | } // namespace libguarded 212 | 213 | #endif 214 | -------------------------------------------------------------------------------- /test/test_shared.cpp: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Ansel Sermersheim 4 | * 5 | * This file is part of CsLibGuarded. 6 | * 7 | * CsLibGuarded is free software which is released under the BSD 2-Clause license. 8 | * For license details refer to the LICENSE provided with this project. 9 | * 10 | * CsLibGuarded is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | * 14 | * https://opensource.org/licenses/BSD-2-Clause 15 | * 16 | ***********************************************************************/ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | using shared_mutex = std::shared_timed_mutex; 25 | namespace chrono = std::chrono; 26 | 27 | #include 28 | 29 | using namespace libguarded; 30 | 31 | TEST_CASE("Shared guarded 1", "[shared_guarded]") 32 | { 33 | shared_guarded data(0); 34 | 35 | { 36 | auto data_handle = data.lock(); 37 | 38 | ++(*data_handle); 39 | 40 | data_handle.reset(); 41 | data_handle = data.lock(); 42 | } 43 | 44 | { 45 | auto data_handle = data.try_lock(); 46 | 47 | REQUIRE(data_handle != nullptr); 48 | REQUIRE(*data_handle == 1); 49 | 50 | /* These tests must be done from another thread, because on 51 | glibc std::mutex is actually a recursive mutex. */ 52 | 53 | std::atomic th1_ok(true); 54 | std::atomic th2_ok(true); 55 | std::atomic th3_ok(true); 56 | 57 | std::thread th1([&]() { 58 | auto data_handle2 = data.try_lock(); 59 | 60 | if (data_handle2 != nullptr) { 61 | th1_ok = false; 62 | } 63 | }); 64 | 65 | std::thread th2([&]() { 66 | auto data_handle2 = data.try_lock_for(std::chrono::milliseconds(20)); 67 | 68 | if (data_handle2 != nullptr) { 69 | th2_ok = false; 70 | } 71 | }); 72 | 73 | std::thread th3([&]() { 74 | auto data_handle2 = data.try_lock_until(std::chrono::steady_clock::now() + 75 | std::chrono::milliseconds(20)); 76 | if (data_handle2 != nullptr) { 77 | th3_ok = false; 78 | } 79 | }); 80 | 81 | th1.join(); 82 | th2.join(); 83 | th3.join(); 84 | 85 | REQUIRE(th1_ok == true); 86 | REQUIRE(th2_ok == true); 87 | REQUIRE(th3_ok == true); 88 | } 89 | 90 | { 91 | auto data_handle = data.try_lock(); 92 | 93 | REQUIRE(data_handle != nullptr); 94 | REQUIRE(*data_handle == 1); 95 | 96 | std::atomic th1_ok(true); 97 | std::atomic th2_ok(true); 98 | std::atomic th3_ok(true); 99 | 100 | std::thread th1([&]() { 101 | auto data_handle2 = data.try_lock_shared(); 102 | 103 | if (data_handle2 != nullptr) { 104 | th1_ok = false; 105 | } 106 | }); 107 | 108 | std::thread th2([&]() { 109 | auto data_handle2 = data.try_lock_shared_for(std::chrono::milliseconds(20)); 110 | if (data_handle2 != nullptr) { 111 | th2_ok = false; 112 | } 113 | }); 114 | 115 | std::thread th3([&]() { 116 | auto data_handle2 = data.try_lock_shared_until(std::chrono::steady_clock::now() + 117 | std::chrono::milliseconds(20)); 118 | if (data_handle2 != nullptr) { 119 | th3_ok = false; 120 | } 121 | }); 122 | 123 | th1.join(); 124 | th2.join(); 125 | th3.join(); 126 | 127 | REQUIRE(th1_ok == true); 128 | REQUIRE(th2_ok == true); 129 | REQUIRE(th3_ok == true); 130 | } 131 | 132 | { 133 | auto data_handle = data.lock_shared(); 134 | 135 | REQUIRE(data_handle != nullptr); 136 | REQUIRE(*data_handle == 1); 137 | 138 | std::atomic th1_ok(true); 139 | std::atomic th2_ok(true); 140 | std::atomic th3_ok(true); 141 | 142 | std::thread th1([&]() { 143 | auto data_handle2 = data.try_lock_shared(); 144 | 145 | if (data_handle2 == nullptr) { 146 | th1_ok = false; 147 | } 148 | 149 | if (*data_handle2 != 1) { 150 | th1_ok = false; 151 | } 152 | }); 153 | 154 | std::thread th2([&]() { 155 | auto data_handle2 = data.try_lock_shared_for(std::chrono::milliseconds(20)); 156 | if (data_handle2 == nullptr) { 157 | th2_ok = false; 158 | } 159 | 160 | if (*data_handle2 != 1) { 161 | th2_ok = false; 162 | } 163 | }); 164 | 165 | std::thread th3([&]() { 166 | auto data_handle2 = data.try_lock_shared_until(std::chrono::steady_clock::now() + 167 | std::chrono::milliseconds(20)); 168 | if (data_handle2 == nullptr) { 169 | th3_ok = false; 170 | } 171 | 172 | if (*data_handle2 != 1) { 173 | th3_ok = false; 174 | } 175 | }); 176 | 177 | th1.join(); 178 | th2.join(); 179 | th3.join(); 180 | 181 | REQUIRE(th1_ok == true); 182 | REQUIRE(th2_ok == true); 183 | REQUIRE(th3_ok == true); 184 | } 185 | } 186 | 187 | TEST_CASE("Shared guarded 2", "[shared_guarded]") 188 | { 189 | shared_guarded data(0); 190 | 191 | std::thread th1([&data]() { 192 | for (int i = 0; i < 100000; ++i) { 193 | auto data_handle = data.lock(); 194 | ++(*data_handle); 195 | } 196 | }); 197 | 198 | std::thread th2([&data]() { 199 | for (int i = 0; i < 100000; ++i) { 200 | auto data_handle = data.lock(); 201 | ++(*data_handle); 202 | } 203 | }); 204 | 205 | std::thread th3([&data]() { 206 | int last_val = 0; 207 | 208 | while (last_val != 200000) { 209 | auto data_handle = data.lock_shared(); 210 | REQUIRE(last_val <= *data_handle); 211 | last_val = *data_handle; 212 | } 213 | }); 214 | 215 | th1.join(); 216 | th2.join(); 217 | th3.join(); 218 | 219 | auto data_handle = data.lock(); 220 | 221 | REQUIRE(*data_handle == 200000); 222 | } 223 | -------------------------------------------------------------------------------- /src/cs_shared_guarded.h: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Ansel Sermersheim 4 | * 5 | * This file is part of CsLibGuarded. 6 | * 7 | * CsLibGuarded is free software which is released under the BSD 2-Clause license. 8 | * For license details refer to the LICENSE provided with this project. 9 | * 10 | * CsLibGuarded is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | * 14 | * https://opensource.org/licenses/BSD-2-Clause 15 | * 16 | ***********************************************************************/ 17 | 18 | #ifndef CSLIBGUARDED_SHARED_GUARDED_H 19 | #define CSLIBGUARDED_SHARED_GUARDED_H 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | namespace libguarded 26 | { 27 | 28 | /** 29 | \headerfile cs_shared_guarded.h 30 | 31 | This templated class wraps an object and allows only one thread at 32 | a time to modify the protected object. 33 | 34 | This class will use std::shared_timed_mutex for the internal 35 | locking mechanism by default. In C++17 the std::shared_mutex class 36 | is also available. 37 | 38 | The handle returned by the various lock methods is moveable but not 39 | copyable. 40 | */ 41 | template > 42 | class shared_guarded 43 | { 44 | private: 45 | class deleter; 46 | class shared_deleter; 47 | 48 | public: 49 | using handle = std::unique_ptr; 50 | using shared_handle = std::unique_ptr; 51 | 52 | template 53 | shared_guarded(Us &&... data); 54 | 55 | // exclusive access 56 | [[nodiscard]] handle lock(); 57 | [[nodiscard]] handle try_lock(); 58 | 59 | template 60 | [[nodiscard]] handle try_lock_for(const Duration &duration); 61 | 62 | template 63 | [[nodiscard]] handle try_lock_until(const TimePoint &timepoint); 64 | 65 | // shared access, note "shared" in method names 66 | [[nodiscard]] shared_handle lock_shared() const; 67 | [[nodiscard]] shared_handle try_lock_shared() const; 68 | 69 | template 70 | [[nodiscard]] shared_handle try_lock_shared_for(const Duration &duration) const; 71 | 72 | template 73 | [[nodiscard]] shared_handle try_lock_shared_until(const TimePoint &timepoint) const; 74 | 75 | private: 76 | T m_obj; 77 | mutable M m_mutex; 78 | }; 79 | 80 | template 81 | class shared_guarded::deleter 82 | { 83 | public: 84 | using pointer = T *; 85 | 86 | deleter() = default; 87 | deleter(std::unique_lock lock); 88 | 89 | void operator()(T *ptr); 90 | 91 | private: 92 | std::unique_lock m_lock; 93 | }; 94 | 95 | template 96 | shared_guarded::deleter::deleter(std::unique_lock lock) 97 | : m_lock(std::move(lock)) 98 | { 99 | } 100 | 101 | template 102 | void shared_guarded::deleter::operator()(T *) 103 | { 104 | if (m_lock.owns_lock()) { 105 | m_lock.unlock(); 106 | } 107 | } 108 | 109 | template 110 | class shared_guarded::shared_deleter 111 | { 112 | public: 113 | using pointer = const T *; 114 | 115 | shared_deleter() = default; 116 | shared_deleter(L lock); 117 | 118 | void operator()(const T *ptr); 119 | 120 | private: 121 | L m_lock; 122 | }; 123 | 124 | template 125 | shared_guarded::shared_deleter::shared_deleter(L lock) 126 | : m_lock(std::move(lock)) 127 | { 128 | } 129 | 130 | template 131 | void shared_guarded::shared_deleter::operator()(const T *) 132 | { 133 | if (m_lock.owns_lock()) { 134 | m_lock.unlock(); 135 | } 136 | } 137 | 138 | template 139 | template 140 | shared_guarded::shared_guarded(Us &&... data) 141 | : m_obj(std::forward(data)...) 142 | { 143 | } 144 | 145 | template 146 | auto shared_guarded::lock() -> handle 147 | { 148 | std::unique_lock lock(m_mutex); 149 | return handle(&m_obj, deleter(std::move(lock))); 150 | } 151 | 152 | template 153 | auto shared_guarded::try_lock() -> handle 154 | { 155 | std::unique_lock lock(m_mutex, std::try_to_lock); 156 | 157 | if (lock.owns_lock()) { 158 | return handle(&m_obj, deleter(std::move(lock))); 159 | } else { 160 | return handle(nullptr, deleter(std::move(lock))); 161 | } 162 | } 163 | 164 | template 165 | template 166 | auto shared_guarded::try_lock_for(const Duration &duration) -> handle 167 | { 168 | std::unique_lock lock(m_mutex, duration); 169 | 170 | if (lock.owns_lock()) { 171 | return handle(&m_obj, deleter(std::move(lock))); 172 | } else { 173 | return handle(nullptr, deleter(std::move(lock))); 174 | } 175 | } 176 | 177 | template 178 | template 179 | auto shared_guarded::try_lock_until(const TimePoint &timepoint) -> handle 180 | { 181 | std::unique_lock lock(m_mutex, timepoint); 182 | 183 | if (lock.owns_lock()) { 184 | return handle(&m_obj, deleter(std::move(lock))); 185 | } else { 186 | return handle(nullptr, deleter(std::move(lock))); 187 | } 188 | } 189 | 190 | template 191 | auto shared_guarded::lock_shared() const -> shared_handle 192 | { 193 | L lock(m_mutex); 194 | return shared_handle(&m_obj, shared_deleter(std::move(lock))); 195 | } 196 | 197 | template 198 | auto shared_guarded::try_lock_shared() const -> shared_handle 199 | { 200 | L lock(m_mutex, std::try_to_lock); 201 | 202 | if (lock.owns_lock()) { 203 | return shared_handle(&m_obj, shared_deleter(std::move(lock))); 204 | } else { 205 | return shared_handle(nullptr, shared_deleter(std::move(lock))); 206 | } 207 | } 208 | 209 | template 210 | template 211 | auto shared_guarded::try_lock_shared_for(const Duration &d) const -> shared_handle 212 | { 213 | L lock(m_mutex, d); 214 | 215 | if (lock.owns_lock()) { 216 | return shared_handle(&m_obj, shared_deleter(std::move(lock))); 217 | } else { 218 | return shared_handle(nullptr, shared_deleter(std::move(lock))); 219 | } 220 | } 221 | 222 | template 223 | template 224 | auto shared_guarded::try_lock_shared_until(const TimePoint &tp) const -> shared_handle 225 | { 226 | L lock(m_mutex, tp); 227 | 228 | if (lock.owns_lock()) { 229 | return shared_handle(&m_obj, shared_deleter(std::move(lock))); 230 | } else { 231 | return shared_handle(nullptr, shared_deleter(std::move(lock))); 232 | } 233 | } 234 | 235 | } // namespace libguarded 236 | 237 | #endif 238 | -------------------------------------------------------------------------------- /test/test_rcu.cpp: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Ansel Sermersheim 4 | * 5 | * This file is part of CsLibGuarded. 6 | * 7 | * CsLibGuarded is free software which is released under the BSD 2-Clause license. 8 | * For license details refer to the LICENSE provided with this project. 9 | * 10 | * CsLibGuarded is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | * 14 | * https://opensource.org/licenses/BSD-2-Clause 15 | * 16 | ***********************************************************************/ 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | using namespace libguarded; 27 | 28 | TEST_CASE("RCU guarded 1", "[rcu_guarded]") 29 | { 30 | rcu_guarded> my_list; 31 | 32 | { 33 | auto h = my_list.lock_write(); 34 | h->push_back(42); 35 | } 36 | 37 | { 38 | auto h = my_list.lock_read(); 39 | 40 | int count = 0; 41 | for (auto &item : *h) { 42 | ++count; 43 | REQUIRE(item == 42); 44 | } 45 | REQUIRE(count == 1); 46 | } 47 | 48 | { 49 | auto rh = my_list.lock_read(); 50 | auto wh = my_list.lock_write(); 51 | 52 | auto iter = rh->begin(); 53 | 54 | wh->erase(wh->begin()); 55 | 56 | int count = 0; 57 | for (; iter != rh->end(); ++iter) { 58 | ++count; 59 | REQUIRE(*iter == 42); 60 | } 61 | REQUIRE(count == 1); 62 | } 63 | 64 | { 65 | auto h = my_list.lock_read(); 66 | 67 | int count = 0; 68 | volatile int escape = 0; 69 | 70 | for (auto &item : *h) { 71 | escape = item; 72 | ++count; 73 | } 74 | REQUIRE(count == 0); 75 | REQUIRE(escape == 0); 76 | } 77 | 78 | { 79 | constexpr const int num_writers = 8; 80 | std::atomic t_writers_done{0}; 81 | 82 | std::vector threads; 83 | 84 | for (int i = 0; i < num_writers; ++i) { 85 | threads.emplace_back([&]() { 86 | while (! t_writers_done.load()) { 87 | auto rh = my_list.lock_write(); 88 | volatile int escape; 89 | 90 | for (auto item : *rh) { 91 | escape = item; 92 | } 93 | 94 | (void) escape; 95 | } 96 | }); 97 | 98 | threads.emplace_back([&]() { 99 | int count = 0; 100 | 101 | while (! t_writers_done.load() && count < 1000) { 102 | auto wh = my_list.lock_write(); 103 | volatile int escape; 104 | 105 | for (auto item : *wh) { 106 | escape = item; 107 | } 108 | 109 | (void) escape; 110 | 111 | for (int j = 0; j < 2; ++j) { 112 | wh->emplace_back(j); 113 | wh->emplace_front(j - 1); 114 | wh->emplace(wh->begin(), j); 115 | auto iter = wh->begin(); 116 | 117 | for (int k = 0; k < 500; ++k) { 118 | auto last = iter; 119 | ++iter; 120 | 121 | if (iter == wh->end()) { 122 | wh->emplace(last, j - 50); 123 | break; 124 | } 125 | } 126 | 127 | wh->emplace(++(wh->begin()), j); 128 | wh->push_back(j + 4); 129 | wh->push_front(j - 7); 130 | } 131 | 132 | ++count; 133 | } 134 | 135 | ++t_writers_done; 136 | }); 137 | } 138 | 139 | threads.emplace_back([&]() { 140 | while (t_writers_done.load() != num_writers) { 141 | auto wh = my_list.lock_write(); 142 | 143 | for (auto iter = wh->begin(); iter != wh->end();) { 144 | iter = wh->erase(iter); 145 | } 146 | } 147 | 148 | // Do one last time now that writers are finished 149 | auto wh = my_list.lock_write(); 150 | for (auto iter = wh->begin(); iter != wh->end();) { 151 | iter = wh->erase(iter); 152 | } 153 | }); 154 | 155 | for (auto &thread : threads) { 156 | thread.join(); 157 | } 158 | } 159 | 160 | { 161 | auto h = my_list.lock_read(); 162 | 163 | int count = 0; 164 | volatile int escape; 165 | 166 | for (auto &item : *h) { 167 | escape = item; 168 | ++count; 169 | } 170 | 171 | (void) escape; 172 | 173 | REQUIRE(count == 0); 174 | } 175 | } 176 | 177 | // allocation events recorded by mock_allocator 178 | struct event { 179 | size_t size; 180 | bool allocated; // true for allocate(), false for deallocate() 181 | }; 182 | using event_log = std::vector; 183 | 184 | template 185 | class mock_allocator 186 | { 187 | public: 188 | using value_type = T; 189 | 190 | explicit mock_allocator(event_log *log) 191 | : m_log(log) 192 | { 193 | } 194 | 195 | mock_allocator(const mock_allocator& other) 196 | : m_log(other.m_log) 197 | { 198 | } 199 | 200 | // converting copy constructor (requires friend) 201 | template friend class mock_allocator; 202 | template 203 | mock_allocator(const mock_allocator& other) 204 | : m_log(other.m_log) 205 | { 206 | } 207 | 208 | T *allocate(size_t n) { 209 | auto p = std::allocator{}.allocate(n); 210 | m_log->emplace_back(event{n * sizeof(T), true}); 211 | 212 | return p; 213 | } 214 | 215 | void deallocate(T *p, size_t n) { 216 | std::allocator{}.deallocate(p, n); 217 | 218 | if (p != nullptr) { 219 | m_log->emplace_back(event{n * sizeof(T), false}); 220 | } 221 | } 222 | 223 | private: 224 | event_log *const m_log; 225 | }; 226 | 227 | TEST_CASE("RCU guarded 2", "[rcu_guarded]") 228 | { 229 | // large value type makes it easy to distinguish nodes from zombies 230 | // (this avoids any dependency on the private rcu_list node types) 231 | constexpr size_t value_size = 256; 232 | auto is_zombie = [=] (const event& e) { return e.size < value_size; }; 233 | auto is_alloc = [] (const event& e) { return e.allocated; }; 234 | 235 | using T = std::aligned_storage::type; 236 | 237 | event_log log; 238 | { 239 | mock_allocator alloc{&log}; 240 | rcu_guarded>> my_list(alloc); 241 | 242 | auto h = my_list.lock_write(); // allocates zombie 243 | h->emplace_back(); // allocates node 244 | h->erase(h->begin()); // allocates zombie 245 | 246 | // expect 3 allocations, two of which are zombies. just count events, 247 | // do not make assumptions about ordering 248 | REQUIRE(3 == log.size()); 249 | REQUIRE(3 == std::count_if(log.begin(), log.end(), is_alloc)); 250 | REQUIRE(2 == std::count_if(log.begin(), log.end(), is_zombie)); 251 | } 252 | 253 | // expects 3 new deallocations, two of which are zombies 254 | REQUIRE(6 == log.size()); 255 | REQUIRE(3 == std::count_if(log.begin(), log.end(), is_alloc)); 256 | REQUIRE(4 == std::count_if(log.begin(), log.end(), is_zombie)); 257 | } 258 | -------------------------------------------------------------------------------- /src/cs_lr_guarded.h: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Ansel Sermersheim 4 | * 5 | * This file is part of CsLibGuarded. 6 | * 7 | * CsLibGuarded is free software which is released under the BSD 2-Clause license. 8 | * For license details refer to the LICENSE provided with this project. 9 | * 10 | * CsLibGuarded is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | * 14 | * https://opensource.org/licenses/BSD-2-Clause 15 | * 16 | ***********************************************************************/ 17 | 18 | #ifndef CSLIBGUARDED_LR_GUARDED_H 19 | #define CSLIBGUARDED_LR_GUARDED_H 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | namespace libguarded 27 | { 28 | 29 | /** 30 | \headerfile cs_lr_guarded.h 31 | 32 | This templated class wraps an object and allows only one thread at 33 | a time to modify the protected object. 34 | 35 | This class will use std::mutex for the internal locking mechanism 36 | by default. Other classes which are useful for the mutex type are 37 | std::recursive_mutex, std::timed_mutex, and 38 | std::recursive_timed_mutex. 39 | 40 | The handle returned by the various lock methods is moveable and 41 | copyable. 42 | 43 | Internally this class operates by maintaining two copies of the 44 | data and applying the same modifications to each copy in 45 | turn. Therefore the lr_guarded object will consume twice as much 46 | memory as one T plus a small amount of overhead. 47 | 48 | The T class must be copy constructible and copy assignable. 49 | */ 50 | template 51 | class lr_guarded 52 | { 53 | private: 54 | class shared_deleter; 55 | 56 | public: 57 | using shared_handle = std::unique_ptr; 58 | 59 | /** 60 | Construct an lr_guarded object. This constructor will accept any 61 | number of parameters, all of which are forwarded to the constructor of T. 62 | */ 63 | template 64 | lr_guarded(Us &&... data); 65 | 66 | /** 67 | Modify the data by passing a functor. The functor must take 68 | exactly one argument of type T&. The functor will be called 69 | twice, once for each copy of the data. It must make the same 70 | modification for both invocations. 71 | 72 | If the first invocation of the functor throws an exception, the 73 | modification will be rolled back by copying from the unchanged 74 | copy. If the second invocation throws, the modification will be 75 | completed by copying from the changed copy. In either case if the 76 | copy constructor throws, the data is left in an indeterminate state. 77 | */ 78 | template 79 | void modify(Func && f); 80 | 81 | /** 82 | Acquire a shared_handle to the protected object. Always succeeds without blocking. 83 | */ 84 | [[nodiscard]] shared_handle lock_shared() const; 85 | 86 | /** 87 | Acquire a shared_handle to the protected object. Always succeeds without blocking. 88 | */ 89 | [[nodiscard]] shared_handle try_lock_shared() const; 90 | 91 | /** 92 | Acquire a shared_handle to the protected object. Always succeeds without blocking. 93 | */ 94 | template 95 | [[nodiscard]] shared_handle try_lock_shared_for(const Duration & duration) const; 96 | 97 | /** 98 | Acquire a shared_handle to the protected object. Always succeeds without blocking. 99 | */ 100 | template 101 | [[nodiscard]] shared_handle try_lock_shared_until(const TimePoint & timepoint) const; 102 | 103 | private: 104 | class shared_deleter 105 | { 106 | public: 107 | using pointer = const T *; 108 | 109 | shared_deleter() : m_readingCount(nullptr) {} 110 | 111 | shared_deleter(const shared_deleter &) = delete; 112 | shared_deleter& operator=(const shared_deleter&) = delete; 113 | 114 | shared_deleter(shared_deleter && other) 115 | : m_readingCount(other.m_readingCount) 116 | { 117 | other.m_readingCount = nullptr; 118 | } 119 | 120 | shared_deleter& operator=(shared_deleter&& other) & { 121 | m_readingCount = other.m_readingCount; 122 | other.m_readingCount = nullptr; 123 | } 124 | 125 | shared_deleter(std::atomic & readingCount) 126 | : m_readingCount(&readingCount) 127 | { 128 | } 129 | 130 | void operator()(const T * ptr) { 131 | if (ptr && m_readingCount) { 132 | (*m_readingCount)--; 133 | } 134 | } 135 | 136 | private: 137 | std::atomic * m_readingCount; 138 | }; 139 | 140 | T m_left; 141 | T m_right; 142 | std::atomic m_readingLeft; 143 | std::atomic m_countingLeft; 144 | mutable std::atomic m_leftReadCount; 145 | mutable std::atomic m_rightReadCount; 146 | mutable Mutex m_writeMutex; 147 | }; 148 | 149 | template 150 | template 151 | lr_guarded::lr_guarded(Us &&... data) 152 | : m_left(std::forward(data)...), m_right(m_left), m_readingLeft(true), m_countingLeft(true), 153 | m_leftReadCount(0), m_rightReadCount(0) 154 | { 155 | } 156 | 157 | template 158 | template 159 | void lr_guarded::modify(Func && func) 160 | { 161 | // consider looser memory ordering 162 | 163 | std::lock_guard lock(m_writeMutex); 164 | 165 | T *firstWriteLocation; 166 | T *secondWriteLocation; 167 | 168 | bool local_readingLeft = m_readingLeft.load(); 169 | 170 | if (local_readingLeft) { 171 | firstWriteLocation = &m_right; 172 | secondWriteLocation = &m_left; 173 | } else { 174 | firstWriteLocation = &m_left; 175 | secondWriteLocation = &m_right; 176 | } 177 | 178 | try { 179 | func(*firstWriteLocation); 180 | } catch (...) { 181 | *firstWriteLocation = *secondWriteLocation; 182 | throw; 183 | } 184 | 185 | m_readingLeft.store(! local_readingLeft); 186 | 187 | bool local_countingLeft = m_countingLeft.load(); 188 | 189 | if (local_countingLeft) { 190 | while (m_rightReadCount.load() != 0) { 191 | std::this_thread::yield(); 192 | } 193 | 194 | } else { 195 | while (m_leftReadCount.load() != 0) { 196 | std::this_thread::yield(); 197 | } 198 | } 199 | 200 | m_countingLeft.store(!local_countingLeft); 201 | 202 | if (local_countingLeft) { 203 | while (m_leftReadCount.load() != 0) { 204 | std::this_thread::yield(); 205 | } 206 | 207 | } else { 208 | while (m_rightReadCount.load() != 0) { 209 | std::this_thread::yield(); 210 | } 211 | } 212 | 213 | try { 214 | func(*secondWriteLocation); 215 | } catch (...) { 216 | *secondWriteLocation = *firstWriteLocation; 217 | throw; 218 | } 219 | } 220 | 221 | template 222 | auto lr_guarded::lock_shared() const -> shared_handle 223 | { 224 | if (m_countingLeft) { 225 | ++m_leftReadCount; 226 | 227 | if (m_readingLeft) { 228 | return shared_handle(&m_left, shared_deleter(m_leftReadCount)); 229 | } else { 230 | return shared_handle(&m_right, shared_deleter(m_leftReadCount)); 231 | } 232 | 233 | } else { 234 | ++m_rightReadCount; 235 | 236 | if (m_readingLeft) { 237 | return shared_handle(&m_left, shared_deleter(m_rightReadCount)); 238 | } else { 239 | return shared_handle(&m_right, shared_deleter(m_rightReadCount)); 240 | } 241 | } 242 | } 243 | 244 | template 245 | auto lr_guarded::try_lock_shared() const -> shared_handle 246 | { 247 | return lock_shared(); 248 | } 249 | 250 | template 251 | template 252 | auto lr_guarded::try_lock_shared_for(const Duration &) const -> shared_handle 253 | { 254 | return lock_shared(); 255 | } 256 | 257 | template 258 | template 259 | auto lr_guarded::try_lock_shared_until(const TimePoint &) const -> shared_handle 260 | { 261 | return lock_shared(); 262 | } 263 | 264 | } // namespace libguarded 265 | 266 | #endif 267 | -------------------------------------------------------------------------------- /src/cs_plain_guarded.h: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Ansel Sermersheim 4 | * 5 | * This file is part of CsLibGuarded. 6 | * 7 | * CsLibGuarded is free software which is released under the BSD 2-Clause license. 8 | * For license details refer to the LICENSE provided with this project. 9 | * 10 | * CsLibGuarded is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | * 14 | * https://opensource.org/licenses/BSD-2-Clause 15 | * 16 | ***********************************************************************/ 17 | 18 | #ifndef CSLIBGUARDED_PLAIN_GUARDED_H 19 | #define CSLIBGUARDED_PLAIN_GUARDED_H 20 | 21 | #include 22 | #include 23 | 24 | namespace libguarded 25 | { 26 | 27 | /** 28 | \headerfile cs_plain_guarded.h 29 | 30 | This templated class wraps an object and allows only one thread at a 31 | time to access the protected object. 32 | 33 | This class will use std::mutex for the internal locking mechanism by 34 | default. Other classes which are useful for the mutex type are 35 | std::recursive_mutex, std::timed_mutex, and 36 | std::recursive_timed_mutex. 37 | 38 | The handle returned by the various lock methods is moveable but not 39 | copyable. 40 | */ 41 | template 42 | class plain_guarded 43 | { 44 | private: 45 | class deleter; 46 | class const_deleter; 47 | 48 | public: 49 | using handle = std::unique_ptr; 50 | using const_handle = std::unique_ptr; 51 | 52 | /** 53 | Construct a guarded object. This constructor will accept any 54 | number of parameters, all of which are forwarded to the 55 | constructor of T. 56 | */ 57 | template 58 | plain_guarded(Us &&... data); 59 | 60 | /** 61 | Acquire a handle to the protected object. As a side effect, the 62 | protected object will be locked from access by any other 63 | thread. The lock will be automatically released when the handle 64 | is destroyed. 65 | */ 66 | [[nodiscard]] handle lock(); 67 | [[nodiscard]] const_handle lock() const; 68 | 69 | /** 70 | Attempt to acquire a handle to the protected object. Returns a 71 | null handle if the object is already locked. As a side effect, 72 | the protected object will be locked from access by any other 73 | thread. The lock will be automatically released when the handle 74 | is destroyed. 75 | */ 76 | [[nodiscard]] handle try_lock(); 77 | [[nodiscard]] const_handle try_lock() const; 78 | 79 | /** 80 | Attempt to acquire a handle to the protected object. As a side 81 | effect, the protected object will be locked from access by any 82 | other thread. The lock will be automatically released when the 83 | handle is destroyed. 84 | 85 | Returns a null handle if the object is already locked, and does 86 | not become available for locking before the time duration has 87 | elapsed. 88 | 89 | Calling this method requires that the underlying mutex type M 90 | supports the try_lock_for method. This is not true if M is the 91 | default std::mutex. 92 | */ 93 | template 94 | [[nodiscard]] handle try_lock_for(const Duration &duration); 95 | template 96 | [[nodiscard]] const_handle try_lock_for(const Duration &duration) const; 97 | 98 | /** 99 | Attempt to acquire a handle to the protected object. As a side 100 | effect, the protected object will be locked from access by any other 101 | thread. The lock will be automatically released when the handle is 102 | destroyed. 103 | 104 | Returns a null handle if the object is already locked, and does not 105 | become available for locking before reaching the specified timepoint. 106 | 107 | Calling this method requires that the underlying mutex type M 108 | supports the try_lock_until method. This is not true if M is the 109 | default std::mutex. 110 | */ 111 | template 112 | [[nodiscard]] handle try_lock_until(const TimePoint &timepoint); 113 | template 114 | [[nodiscard]] const_handle try_lock_until(const TimePoint &timepoint) const; 115 | 116 | private: 117 | T m_obj; 118 | mutable M m_mutex; 119 | }; 120 | 121 | template 122 | class plain_guarded::deleter 123 | { 124 | public: 125 | using pointer = T *; 126 | 127 | deleter() = default; 128 | deleter(std::unique_lock lock); 129 | 130 | void operator()(T *ptr); 131 | 132 | private: 133 | std::unique_lock m_lock; 134 | }; 135 | 136 | template 137 | plain_guarded::deleter::deleter(std::unique_lock lock) 138 | : m_lock(std::move(lock)) 139 | { 140 | } 141 | 142 | template 143 | void plain_guarded::deleter::operator()(T *) 144 | { 145 | if (m_lock.owns_lock()) { 146 | m_lock.unlock(); 147 | } 148 | } 149 | 150 | template 151 | class plain_guarded::const_deleter 152 | { 153 | public: 154 | using pointer = const T *; 155 | 156 | const_deleter() = default; 157 | const_deleter(std::unique_lock lock); 158 | 159 | void operator()(const T *ptr); 160 | 161 | private: 162 | std::unique_lock m_lock; 163 | }; 164 | 165 | template 166 | plain_guarded::const_deleter::const_deleter(std::unique_lock lock) 167 | : m_lock(std::move(lock)) 168 | { 169 | } 170 | 171 | template 172 | void plain_guarded::const_deleter::operator()(const T *) 173 | { 174 | if (m_lock.owns_lock()) { 175 | m_lock.unlock(); 176 | } 177 | } 178 | 179 | template 180 | template 181 | plain_guarded::plain_guarded(Us &&... data) 182 | : m_obj(std::forward(data)...) 183 | { 184 | } 185 | 186 | template 187 | auto plain_guarded::lock() -> handle 188 | { 189 | std::unique_lock lock(m_mutex); 190 | return handle(&m_obj, deleter(std::move(lock))); 191 | } 192 | 193 | template 194 | auto plain_guarded::lock() const -> const_handle 195 | { 196 | std::unique_lock lock(m_mutex); 197 | return const_handle(&m_obj, const_deleter(std::move(lock))); 198 | } 199 | 200 | template 201 | auto plain_guarded::try_lock() -> handle 202 | { 203 | std::unique_lock lock(m_mutex, std::try_to_lock); 204 | 205 | if (lock.owns_lock()) { 206 | return handle(&m_obj, deleter(std::move(lock))); 207 | } else { 208 | return handle(nullptr, deleter(std::move(lock))); 209 | } 210 | } 211 | 212 | template 213 | auto plain_guarded::try_lock() const -> const_handle 214 | { 215 | std::unique_lock lock(m_mutex, std::try_to_lock); 216 | 217 | if (lock.owns_lock()) { 218 | return const_handle(&m_obj, const_deleter(std::move(lock))); 219 | } else { 220 | return const_handle(nullptr, const_deleter(std::move(lock))); 221 | } 222 | } 223 | 224 | template 225 | template 226 | auto plain_guarded::try_lock_for(const Duration &d) -> handle 227 | { 228 | std::unique_lock lock(m_mutex, d); 229 | 230 | if (lock.owns_lock()) { 231 | return handle(&m_obj, deleter(std::move(lock))); 232 | } else { 233 | return handle(nullptr, deleter(std::move(lock))); 234 | } 235 | } 236 | 237 | template 238 | template 239 | auto plain_guarded::try_lock_for(const Duration &d) const -> const_handle 240 | { 241 | std::unique_lock lock(m_mutex, d); 242 | 243 | if (lock.owns_lock()) { 244 | return const_handle(&m_obj, const_deleter(std::move(lock))); 245 | } else { 246 | return const_handle(nullptr, const_deleter(std::move(lock))); 247 | } 248 | } 249 | 250 | template 251 | template 252 | auto plain_guarded::try_lock_until(const TimePoint &tp) -> handle 253 | { 254 | std::unique_lock lock(m_mutex, tp); 255 | 256 | if (lock.owns_lock()) { 257 | return handle(&m_obj, deleter(std::move(lock))); 258 | } else { 259 | return handle(nullptr, deleter(std::move(lock))); 260 | } 261 | } 262 | 263 | template 264 | template 265 | auto plain_guarded::try_lock_until(const TimePoint &tp) const -> const_handle 266 | { 267 | std::unique_lock lock(m_mutex, tp); 268 | 269 | if (lock.owns_lock()) { 270 | return const_handle(&m_obj, const_deleter(std::move(lock))); 271 | } else { 272 | return const_handle(nullptr, const_deleter(std::move(lock))); 273 | } 274 | } 275 | 276 | template 277 | using guarded [[deprecated("renamed to plain_guarded")]] = plain_guarded; 278 | 279 | } // namespace libguarded 280 | 281 | #endif 282 | -------------------------------------------------------------------------------- /src/cs_deferred_guarded.h: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Ansel Sermersheim 4 | * 5 | * This file is part of CsLibGuarded. 6 | * 7 | * CsLibGuarded is free software which is released under the BSD 2-Clause license. 8 | * For license details refer to the LICENSE provided with this project. 9 | * 10 | * CsLibGuarded is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | * 14 | * https://opensource.org/licenses/BSD-2-Clause 15 | * 16 | ***********************************************************************/ 17 | 18 | #ifndef CSLIBGUARDED_DEFERRED_GUARDED_H 19 | #define CSLIBGUARDED_DEFERRED_GUARDED_H 20 | 21 | #include "cs_plain_guarded.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | namespace libguarded 30 | { 31 | 32 | template 33 | typename std::add_lvalue_reference::type declref(); 34 | 35 | /** 36 | \headerfile cs_deferred_guarded.h 37 | 38 | This templated 39 | class wraps an object and allows only one thread at a time to 40 | modify the protected object. 41 | 42 | This class will use std::shared_timed_mutex for the internal locking mechanism by 43 | default. In C++17, the class std::shared_mutex is available as well. 44 | 45 | The shared_handle returned by the various lock methods is moveable 46 | but not copyable. 47 | */ 48 | template 49 | class deferred_guarded 50 | { 51 | private: 52 | class shared_deleter; 53 | 54 | public: 55 | using shared_handle = std::unique_ptr; 56 | 57 | template 58 | deferred_guarded(Us &&... data); 59 | 60 | template 61 | void modify_detach(Func && func); 62 | 63 | template 64 | [[nodiscard]] auto modify_async(Func && func) -> 65 | typename std::future()(declref()))>; 66 | 67 | [[nodiscard]] shared_handle lock_shared() const; 68 | [[nodiscard]] shared_handle try_lock_shared() const; 69 | 70 | template 71 | [[nodiscard]] shared_handle try_lock_shared_for(const Duration & duration) const; 72 | 73 | template 74 | [[nodiscard]] shared_handle try_lock_shared_until(const TimePoint & timepoint) const; 75 | 76 | private: 77 | class shared_deleter 78 | { 79 | public: 80 | using pointer = const T *; 81 | 82 | shared_deleter(M & mutex) 83 | : m_deleter_mutex(mutex) 84 | { 85 | } 86 | 87 | void operator()(const T * ptr) 88 | { 89 | if (ptr) { 90 | m_deleter_mutex.unlock_shared(); 91 | } 92 | } 93 | 94 | private: 95 | M & m_deleter_mutex; 96 | }; 97 | 98 | void do_pending_writes() const; 99 | 100 | mutable T m_obj; 101 | mutable M m_mutex; 102 | 103 | mutable std::atomic m_pendingWrites; 104 | 105 | mutable plain_guarded>> m_pendingList; 106 | }; 107 | 108 | template 109 | template 110 | deferred_guarded::deferred_guarded(Us &&... data) 111 | : m_obj(std::forward(data)...), m_pendingWrites(false) 112 | { 113 | } 114 | 115 | template 116 | template 117 | void deferred_guarded::modify_detach(Func && func) 118 | { 119 | std::unique_lock lock(m_mutex, std::try_to_lock); 120 | 121 | if (lock.owns_lock()) { 122 | // consider looser memory ordering 123 | if (m_pendingWrites.load()) { 124 | std::vector> localPending; 125 | m_pendingWrites.store(false); 126 | swap(localPending, *(m_pendingList.lock())); 127 | 128 | for (auto & f : localPending) { 129 | f(m_obj); 130 | } 131 | } 132 | 133 | func(m_obj); 134 | 135 | } else { 136 | m_pendingList.lock()->push_back(std::packaged_task(std::forward(func))); 137 | m_pendingWrites.store(true); 138 | } 139 | } 140 | 141 | template 142 | auto call_returning_future(Func & func, T & data) -> 143 | typename std::enable_if::value, std::future>::type 144 | { 145 | std::promise promise; 146 | 147 | try { 148 | promise.set_value(func(data)); 149 | } catch (...) { 150 | promise.set_exception(std::current_exception()); 151 | } 152 | 153 | return promise.get_future(); 154 | } 155 | 156 | template 157 | auto call_returning_future(Func & func, T & data) -> 158 | typename std::enable_if::value, std::future>::type 159 | { 160 | std::promise promise; 161 | 162 | try { 163 | func(data); 164 | promise.set_value(); 165 | } catch (...) { 166 | promise.set_exception(std::current_exception()); 167 | } 168 | 169 | return promise.get_future(); 170 | } 171 | 172 | template 173 | auto package_task_void(Func && func) -> 174 | typename std::enable_if::value, 175 | std::pair, std::future>>::type 176 | { 177 | std::packaged_task task(std::forward(func)); 178 | std::future task_future(task.get_future()); 179 | 180 | return std::make_pair(std::move(task), std::move(task_future)); 181 | } 182 | 183 | template 184 | auto package_task_void(Func && func) -> 185 | typename std::enable_if::value, 186 | std::pair, std::future>>::type 187 | { 188 | std::packaged_task task(std::forward(func)); 189 | std::future task_future(task.get_future()); 190 | 191 | return std::make_pair(std::packaged_task(std::move(task)), std::move(task_future)); 192 | } 193 | 194 | template 195 | template 196 | auto deferred_guarded::modify_async(Func && func) -> 197 | typename std::future()(declref()))> 198 | { 199 | using return_t = decltype(func(m_obj)); 200 | using future_t = std::future; 201 | future_t retval; 202 | 203 | std::unique_lock lock(m_mutex, std::try_to_lock); 204 | 205 | if (lock.owns_lock()) { 206 | if (m_pendingWrites.load()) { 207 | std::vector> localPending; 208 | 209 | m_pendingWrites.store(false); 210 | swap(localPending, *(m_pendingList.lock())); 211 | for (auto & f : localPending) { 212 | f(m_obj); 213 | } 214 | } 215 | 216 | retval = call_returning_future(func, m_obj); 217 | 218 | } else { 219 | std::pair, std::future> task_future = 220 | package_task_void(std::forward(func)); 221 | 222 | retval = std::move(task_future.second); 223 | 224 | m_pendingList.lock()->push_back(std::move(task_future.first)); 225 | m_pendingWrites.store(true); 226 | } 227 | 228 | return retval; 229 | } 230 | 231 | template 232 | void deferred_guarded::do_pending_writes() const 233 | { 234 | if (m_pendingWrites.load()) { 235 | 236 | std::unique_lock lock(m_mutex, std::try_to_lock); 237 | 238 | if (lock.owns_lock()) { 239 | if (m_pendingWrites.load()) { 240 | std::vector> localPending; 241 | 242 | m_pendingWrites.store(false); 243 | swap(localPending, *(m_pendingList.lock())); 244 | 245 | for (auto & f : localPending) { 246 | f(m_obj); 247 | } 248 | } 249 | } 250 | } 251 | } 252 | 253 | template 254 | auto deferred_guarded::lock_shared() const -> shared_handle 255 | { 256 | do_pending_writes(); 257 | m_mutex.lock_shared(); 258 | 259 | return shared_handle(&m_obj, shared_deleter(m_mutex)); 260 | } 261 | 262 | template 263 | auto deferred_guarded::try_lock_shared() const -> shared_handle 264 | { 265 | do_pending_writes(); 266 | if (m_mutex.try_lock_shared()) { 267 | return shared_handle(&m_obj, shared_deleter(m_mutex)); 268 | } else { 269 | return shared_handle(nullptr, shared_deleter(m_mutex)); 270 | } 271 | } 272 | 273 | template 274 | template 275 | auto deferred_guarded::try_lock_shared_for(const Duration & d) const -> shared_handle 276 | { 277 | do_pending_writes(); 278 | if (m_mutex.try_lock_shared_for(d)) { 279 | return shared_handle(&m_obj, shared_deleter(m_mutex)); 280 | } else { 281 | return shared_handle(nullptr, shared_deleter(m_mutex)); 282 | } 283 | } 284 | 285 | template 286 | template 287 | auto deferred_guarded::try_lock_shared_until(const TimePoint & tp) const -> shared_handle 288 | { 289 | do_pending_writes(); 290 | if (m_mutex.try_lock_shared_until(tp)) { 291 | return shared_handle(&m_obj, shared_deleter(m_mutex)); 292 | } else { 293 | return shared_handle(nullptr, shared_deleter(m_mutex)); 294 | } 295 | } 296 | 297 | } // namespace libguarded 298 | 299 | #endif 300 | -------------------------------------------------------------------------------- /src/cs_cow_guarded.h: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Ansel Sermersheim 4 | * 5 | * This file is part of CsLibGuarded. 6 | * 7 | * CsLibGuarded is free software which is released under the BSD 2-Clause license. 8 | * For license details refer to the LICENSE provided with this project. 9 | * 10 | * CsLibGuarded is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | * 14 | * https://opensource.org/licenses/BSD-2-Clause 15 | * 16 | ***********************************************************************/ 17 | 18 | #ifndef CSLIBGUARDED_COW_GUARDED_H 19 | #define CSLIBGUARDED_COW_GUARDED_H 20 | 21 | #include "cs_lr_guarded.h" 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | namespace libguarded 28 | { 29 | 30 | /** 31 | \headerfile cs_cow_guarded.h 32 | 33 | This templated class wraps an object and allows only one thread at a 34 | time to modify the protected object. Any number of threads can read 35 | the protected object simultaneously and the version as of the time of 36 | reading will be maintained until no longer needed. 37 | 38 | When a thread locks the cow_guarded object for writing, a copy is 39 | made so that the writer may modify the stored data without race 40 | conditions. When the handle is released, the old copy is atomically 41 | replaced by the modified copy. 42 | 43 | The handle type supports a cancel() method, which will discard the 44 | modified copy and immediately unlock the data without applying the 45 | changes. 46 | 47 | This class will use std::mutex for the internal locking mechanism by 48 | default. Other classes which are useful for the mutex type are 49 | std::recursive_mutex, std::timed_mutex, and 50 | std::recursive_timed_mutex. 51 | 52 | The handle returned by the various lock methods is moveable but not 53 | copyable. The shared_handle type is moveable and copyable. 54 | 55 | The T class must be copy constructible. 56 | */ 57 | template 58 | class cow_guarded 59 | { 60 | private: 61 | class deleter; 62 | class shared_deleter; 63 | 64 | public: 65 | class handle; 66 | using shared_handle = std::shared_ptr; 67 | 68 | /** 69 | Construct a cow_guarded object. This constructor will accept any 70 | number of parameters, all of which are forwarded to the constructor of T. 71 | */ 72 | template 73 | cow_guarded(Us &&... data); 74 | 75 | /** 76 | Acquire a handle to the protected object. As a side effect, the 77 | protected object will be locked from access by any other 78 | thread. The lock will be automatically released when the handle 79 | is destroyed. 80 | */ 81 | handle lock(); 82 | 83 | /** 84 | Attempt to acquire a handle to the protected object. Returns a 85 | null handle if the object is already locked. As a side effect, 86 | the protected object will be locked from access by any other 87 | thread. The lock will be automatically released when the handle 88 | is destroyed. 89 | */ 90 | handle try_lock(); 91 | 92 | /** 93 | Attempt to acquire a handle to the protected object. As a side 94 | effect, the protected object will be locked from access by any 95 | other thread. The lock will be automatically released when the 96 | handle is destroyed. 97 | 98 | Returns a null handle if the object is already locked, and does 99 | not become available for locking before the time duration has 100 | elapsed. 101 | 102 | Calling this method requires that the underlying mutex type M 103 | supports the try_lock_for method. This is not true if M is the 104 | default std::mutex. 105 | */ 106 | template 107 | handle try_lock_for(const Duration &duration); 108 | 109 | /** 110 | Attempt to acquire a handle to the protected object. As a side 111 | effect, the protected object will be locked from access by any other 112 | thread. The lock will be automatically released when the handle is 113 | destroyed. 114 | 115 | Returns a null handle if the object is already locked, and does not 116 | become available for locking before reaching the specified timepoint. 117 | 118 | Calling this method requires that the underlying mutex type M 119 | supports the try_lock_until method. This is not true if M is the 120 | default std::mutex. 121 | */ 122 | template 123 | handle try_lock_until(const TimePoint &timepoint); 124 | 125 | /** 126 | Acquire a shared_handle to the protected object. Always succeeds without 127 | blocking. 128 | */ 129 | shared_handle lock_shared() const; 130 | 131 | /** 132 | Acquire a shared_handle to the protected object. Always succeeds without 133 | blocking. 134 | */ 135 | shared_handle try_lock_shared() const; 136 | 137 | /** 138 | Acquire a shared_handle to the protected object. Always succeeds without 139 | blocking. 140 | */ 141 | template 142 | shared_handle try_lock_shared_for(const Duration &duration) const; 143 | 144 | /** 145 | Acquire a shared_handle to the protected object. Always succeeds without 146 | blocking. 147 | */ 148 | template 149 | shared_handle try_lock_shared_until(const TimePoint &timepoint) const; 150 | 151 | private: 152 | class deleter 153 | { 154 | public: 155 | using pointer = T *; 156 | 157 | deleter() = default; 158 | 159 | deleter(std::unique_lock &&lock, cow_guarded &guarded) 160 | : m_lock(std::move(lock)), m_guarded(&guarded), m_cancelled(false) 161 | { 162 | } 163 | 164 | void cancel() { 165 | m_cancelled = true; 166 | 167 | if (m_lock.owns_lock()) { 168 | m_lock.unlock(); 169 | } 170 | } 171 | 172 | void operator()(T *ptr) { 173 | if (m_cancelled) { 174 | delete ptr; 175 | 176 | } else if (ptr && m_guarded) { 177 | std::shared_ptr newPtr(ptr); 178 | 179 | m_guarded->m_data.modify([newPtr](std::shared_ptr &tmpPtr) { tmpPtr = newPtr; }); 180 | } 181 | 182 | if (m_lock.owns_lock()) { 183 | m_lock.unlock(); 184 | } 185 | } 186 | 187 | private: 188 | std::unique_lock m_lock; 189 | cow_guarded *m_guarded; 190 | bool m_cancelled; 191 | }; 192 | 193 | public: 194 | /** 195 | The handle class for cow_guarded is moveable but not copyable. 196 | */ 197 | class handle : public std::unique_ptr 198 | { 199 | public: 200 | using std::unique_ptr::unique_ptr; 201 | 202 | /** 203 | Cancel all pending changes, reset the handle to null, and unlock the data. 204 | */ 205 | void cancel() { 206 | this->get_deleter().cancel(); 207 | this->reset(); 208 | } 209 | }; 210 | 211 | private: 212 | mutable lr_guarded> m_data; 213 | mutable Mutex m_writeMutex; 214 | }; 215 | 216 | template 217 | template 218 | cow_guarded::cow_guarded(Us &&... data) 219 | : m_data(std::make_shared(std::forward(data)...)) 220 | { 221 | } 222 | 223 | template 224 | auto cow_guarded::lock() -> handle 225 | { 226 | std::unique_lock guard(m_writeMutex); 227 | 228 | auto data(m_data.lock_shared()); 229 | std::unique_ptr val(new T(**data)); 230 | data.reset(); 231 | 232 | return handle(val.release(), deleter(std::move(guard), *this)); 233 | } 234 | 235 | template 236 | auto cow_guarded::try_lock() -> handle 237 | { 238 | std::unique_lock guard(m_writeMutex, std::try_to_lock); 239 | 240 | if (!guard.owns_lock()) { 241 | return handle(); 242 | } 243 | 244 | // lr_guarded::lock_shared cannot block or fail 245 | auto data(m_data.lock_shared()); 246 | 247 | std::unique_ptr val(new T(**data)); 248 | data.reset(); 249 | 250 | return handle(val.release(), deleter(std::move(guard), *this)); 251 | } 252 | 253 | template 254 | template 255 | auto cow_guarded::try_lock_for(const Duration &duration) -> handle 256 | { 257 | std::unique_lock guard(m_writeMutex, duration); 258 | 259 | if (!guard.owns_lock()) { 260 | return handle(); 261 | } 262 | 263 | // lr_guarded::lock_shared cannot block or fail 264 | auto data = m_data.lock_shared(); 265 | 266 | std::unique_ptr val(new T(**data)); 267 | data.reset(); 268 | 269 | return handle(val.release(), deleter(std::move(guard), *this)); 270 | } 271 | 272 | template 273 | template 274 | auto cow_guarded::try_lock_until(const TimePoint &timepoint) -> handle 275 | { 276 | std::unique_lock guard(m_writeMutex, timepoint); 277 | 278 | if (! guard.owns_lock()) { 279 | return handle(); 280 | } 281 | 282 | // lr_guarded::lock_shared cannot block or fail 283 | auto data(m_data.lock_shared()); 284 | 285 | std::unique_ptr val(new T(**data)); 286 | data.reset(); 287 | 288 | return handle(val.release(), deleter(std::move(guard), *this)); 289 | } 290 | 291 | template 292 | auto cow_guarded::lock_shared() const -> shared_handle 293 | { 294 | auto lock = m_data.lock_shared(); 295 | return *lock; 296 | } 297 | 298 | template 299 | auto cow_guarded::try_lock_shared() const -> shared_handle 300 | { 301 | shared_handle retval; 302 | 303 | auto lock = m_data.try_lock_shared(); 304 | if (lock) { 305 | retval = *lock; 306 | } 307 | 308 | return retval; 309 | } 310 | 311 | template 312 | template 313 | auto cow_guarded::try_lock_shared_for(const Duration &duration) const -> shared_handle 314 | { 315 | shared_handle retval; 316 | 317 | auto lock = m_data.try_lock_shared_for(duration); 318 | if (lock) { 319 | retval = *lock; 320 | } 321 | 322 | return retval; 323 | } 324 | 325 | template 326 | template 327 | auto cow_guarded::try_lock_shared_until(const TimePoint &timepoint) const -> shared_handle 328 | { 329 | shared_handle retval; 330 | 331 | auto lock = m_data.try_lock_shared_until(timepoint); 332 | if (lock) { 333 | retval = *lock; 334 | } 335 | 336 | return retval; 337 | } 338 | 339 | } // namespace libguarded 340 | 341 | #endif 342 | -------------------------------------------------------------------------------- /cmake/modules/ParseAndAddCatchTests.cmake: -------------------------------------------------------------------------------- 1 | #==================================================================================================# 2 | # supported macros # 3 | # - TEST_CASE, # 4 | # - TEMPLATE_TEST_CASE # 5 | # - SCENARIO, # 6 | # - TEST_CASE_METHOD, # 7 | # - CATCH_TEST_CASE, # 8 | # - CATCH_TEMPLATE_TEST_CASE # 9 | # - CATCH_SCENARIO, # 10 | # - CATCH_TEST_CASE_METHOD. # 11 | # # 12 | # Usage # 13 | # 1. make sure this module is in the path or add this otherwise: # 14 | # set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/") # 15 | # 2. make sure that you've enabled testing option for the project by the call: # 16 | # enable_testing() # 17 | # 3. add the lines to the script for testing target (sample CMakeLists.txt): # 18 | # project(testing_target) # 19 | # set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/") # 20 | # enable_testing() # 21 | # # 22 | # find_path(CATCH_INCLUDE_DIR "catch.hpp") # 23 | # include_directories(${INCLUDE_DIRECTORIES} ${CATCH_INCLUDE_DIR}) # 24 | # # 25 | # file(GLOB SOURCE_FILES "*.cpp") # 26 | # add_executable(${PROJECT_NAME} ${SOURCE_FILES}) # 27 | # # 28 | # include(ParseAndAddCatchTests) # 29 | # ParseAndAddCatchTests(${PROJECT_NAME}) # 30 | # # 31 | # The following variables affect the behavior of the script: # 32 | # # 33 | # PARSE_CATCH_TESTS_VERBOSE (Default OFF) # 34 | # -- enables debug messages # 35 | # PARSE_CATCH_TESTS_NO_HIDDEN_TESTS (Default OFF) # 36 | # -- excludes tests marked with [!hide], [.] or [.foo] tags # 37 | # PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME (Default ON) # 38 | # -- adds fixture class name to the test name # 39 | # PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME (Default ON) # 40 | # -- adds cmake target name to the test name # 41 | # PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS (Default OFF) # 42 | # -- causes CMake to rerun when file with tests changes so that new tests will be discovered # 43 | # # 44 | # One can also set (locally) the optional variable OptionalCatchTestLauncher to precise the way # 45 | # a test should be run. For instance to use test MPI, one can write # 46 | # set(OptionalCatchTestLauncher ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${NUMPROC}) # 47 | # just before calling this ParseAndAddCatchTests function # 48 | # # 49 | # The AdditionalCatchParameters optional variable can be used to pass extra argument to the test # 50 | # command. For example, to include successful tests in the output, one can write # 51 | # set(AdditionalCatchParameters --success) # 52 | # # 53 | # After the script, the ParseAndAddCatchTests_TESTS property for the target, and for each source # 54 | # file in the target is set, and contains the list of the tests extracted from that target, or # 55 | # from that file. This is useful, for example to add further labels or properties to the tests. # 56 | # # 57 | #==================================================================================================# 58 | 59 | if (CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.8) 60 | message(FATAL_ERROR "ParseAndAddCatchTests requires CMake 2.8.8 or newer") 61 | endif() 62 | 63 | option(PARSE_CATCH_TESTS_VERBOSE "Print Catch to CTest parser debug messages" OFF) 64 | option(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS "Exclude tests with [!hide], [.] or [.foo] tags" OFF) 65 | option(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME "Add fixture class name to the test name" ON) 66 | option(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME "Add target name to the test name" ON) 67 | option(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS "Add test file to CMAKE_CONFIGURE_DEPENDS property" OFF) 68 | 69 | function(ParseAndAddCatchTests_PrintDebugMessage) 70 | if(PARSE_CATCH_TESTS_VERBOSE) 71 | message(STATUS "ParseAndAddCatchTests: ${ARGV}") 72 | endif() 73 | endfunction() 74 | 75 | # This removes the contents between 76 | # - block comments (i.e. /* ... */) 77 | # - full line comments (i.e. // ... ) 78 | # contents have been read into '${CppCode}'. 79 | # !keep partial line comments 80 | function(ParseAndAddCatchTests_RemoveComments CppCode) 81 | string(ASCII 2 CMakeBeginBlockComment) 82 | string(ASCII 3 CMakeEndBlockComment) 83 | string(REGEX REPLACE "/\\*" "${CMakeBeginBlockComment}" ${CppCode} "${${CppCode}}") 84 | string(REGEX REPLACE "\\*/" "${CMakeEndBlockComment}" ${CppCode} "${${CppCode}}") 85 | string(REGEX REPLACE "${CMakeBeginBlockComment}[^${CMakeEndBlockComment}]*${CMakeEndBlockComment}" "" ${CppCode} "${${CppCode}}") 86 | string(REGEX REPLACE "\n[ \t]*//+[^\n]+" "\n" ${CppCode} "${${CppCode}}") 87 | 88 | set(${CppCode} "${${CppCode}}" PARENT_SCOPE) 89 | endfunction() 90 | 91 | # Worker function 92 | function(ParseAndAddCatchTests_ParseFile SourceFile TestTarget) 93 | # If SourceFile is an object library, do not scan it (as it is not a file). Exit without giving a warning about a missing file. 94 | if(SourceFile MATCHES "\\\$") 95 | ParseAndAddCatchTests_PrintDebugMessage("Detected OBJECT library: ${SourceFile} this will not be scanned for tests.") 96 | return() 97 | endif() 98 | 99 | # According to CMake docs EXISTS behavior is well-defined only for full paths. 100 | get_filename_component(SourceFile ${SourceFile} ABSOLUTE) 101 | if(NOT EXISTS ${SourceFile}) 102 | get_property(isGenerated SOURCE ${SourceFile} PROPERTY GENERATED) 103 | 104 | if(NOT isGenerated) 105 | message(WARNING "Cannot find source file: ${SourceFile}") 106 | endif() 107 | 108 | return() 109 | endif() 110 | 111 | ParseAndAddCatchTests_PrintDebugMessage("parsing ${SourceFile}") 112 | file(STRINGS ${SourceFile} Contents NEWLINE_CONSUME) 113 | 114 | # Remove block and fullline comments 115 | ParseAndAddCatchTests_RemoveComments(Contents) 116 | 117 | # Find definition of test names 118 | string(REGEX MATCHALL "[ \t]*(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([ \t\n]*\"[^\"]*\"[ \t\n]*,[ \t\n]*\"[^\"]*\"([^\(\)]+(\\([^\)]*\\))*)*\\)+[ \t\n]*{+[ \t]*(//[^\n]*[Tt][Ii][Mm][Ee][Oo][Uu][Tt][ \t]*[0-9]+)*" Tests "${Contents}") 119 | 120 | if(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS AND Tests) 121 | ParseAndAddCatchTests_PrintDebugMessage("Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property") 122 | set_property( 123 | DIRECTORY 124 | APPEND 125 | PROPERTY CMAKE_CONFIGURE_DEPENDS ${SourceFile} 126 | ) 127 | endif() 128 | 129 | foreach(TestName ${Tests}) 130 | # Strip newlines 131 | string(REGEX REPLACE "\\\\\n|\n" "" TestName "${TestName}") 132 | 133 | # Get test type and fixture if applicable 134 | string(REGEX MATCH "(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^,^\"]*" TestTypeAndFixture "${TestName}") 135 | string(REGEX MATCH "(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)" TestType "${TestTypeAndFixture}") 136 | string(REGEX REPLACE "${TestType}\\([ \t]*" "" TestFixture "${TestTypeAndFixture}") 137 | 138 | # Get string parts of test definition 139 | string(REGEX MATCHALL "\"+([^\\^\"]|\\\\\")+\"+" TestStrings "${TestName}") 140 | 141 | # Strip wrapping quotation marks 142 | string(REGEX REPLACE "^\"(.*)\"$" "\\1" TestStrings "${TestStrings}") 143 | string(REPLACE "\";\"" ";" TestStrings "${TestStrings}") 144 | 145 | # Validate that a test name and tags have been provided 146 | list(LENGTH TestStrings TestStringsLength) 147 | if(TestStringsLength GREATER 2 OR TestStringsLength LESS 1) 148 | message(FATAL_ERROR "You must provide a valid test name and tags for all tests in ${SourceFile}") 149 | endif() 150 | 151 | # Assign name and tags 152 | list(GET TestStrings 0 Name) 153 | if("${TestType}" STREQUAL "SCENARIO") 154 | set(Name "Scenario: ${Name}") 155 | endif() 156 | if(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME AND "${TestType}" MATCHES "(CATCH_)?TEST_CASE_METHOD" AND TestFixture ) 157 | set(CTestName "${TestFixture}:${Name}") 158 | else() 159 | set(CTestName "${Name}") 160 | endif() 161 | if(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME) 162 | set(CTestName "${TestTarget}:${CTestName}") 163 | endif() 164 | # add target to labels to enable running all tests added from this target 165 | set(Labels ${TestTarget}) 166 | if(TestStringsLength EQUAL 2) 167 | list(GET TestStrings 1 Tags) 168 | string(TOLOWER "${Tags}" Tags) 169 | # remove target from labels if the test is hidden 170 | if("${Tags}" MATCHES ".*\\[!?(hide|\\.)\\].*") 171 | list(REMOVE_ITEM Labels ${TestTarget}) 172 | endif() 173 | string(REPLACE "]" ";" Tags "${Tags}") 174 | string(REPLACE "[" "" Tags "${Tags}") 175 | else() 176 | # unset tags variable from previous loop 177 | unset(Tags) 178 | endif() 179 | 180 | list(APPEND Labels ${Tags}) 181 | 182 | set(HiddenTagFound OFF) 183 | foreach(label ${Labels}) 184 | string(REGEX MATCH "^!hide|^\\." result ${label}) 185 | if(result) 186 | set(HiddenTagFound ON) 187 | break() 188 | endif(result) 189 | endforeach(label) 190 | if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_LESS "3.9") 191 | ParseAndAddCatchTests_PrintDebugMessage("Skipping test \"${CTestName}\" as it has [!hide], [.] or [.foo] label") 192 | else() 193 | ParseAndAddCatchTests_PrintDebugMessage("Adding test \"${CTestName}\"") 194 | if(Labels) 195 | ParseAndAddCatchTests_PrintDebugMessage("Setting labels to ${Labels}") 196 | endif() 197 | 198 | # Escape commas in the test spec 199 | string(REPLACE "," "\\," Name ${Name}) 200 | 201 | # Work around CMake 3.18.0 change in `add_test()`, before the escaped quotes were neccessary, 202 | # only with CMake 3.18.0 the escaped double quotes confuse the call. This change is reverted in 3.18.1 203 | if(NOT ${CMAKE_VERSION} VERSION_EQUAL "3.18") 204 | set(CTestName "\"${CTestName}\"") 205 | endif() 206 | 207 | # Handle template test cases 208 | if("${TestTypeAndFixture}" MATCHES ".*TEMPLATE_.*") 209 | set(Name "${Name} - *") 210 | endif() 211 | 212 | # Add the test and set its properties 213 | add_test(NAME "${CTestName}" 214 | WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" 215 | COMMAND ${OptionalCatchTestLauncher} $ ${Name} ${AdditionalCatchParameters}) 216 | 217 | # Old CMake versions do not document VERSION_GREATER_EQUAL, so we use VERSION_GREATER with 3.8 instead 218 | if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_GREATER "3.8") 219 | ParseAndAddCatchTests_PrintDebugMessage("Setting DISABLED test property") 220 | set_tests_properties("${CTestName}" PROPERTIES DISABLED ON) 221 | else() 222 | set_tests_properties("${CTestName}" PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran" 223 | LABELS "${Labels}") 224 | endif() 225 | set_property( 226 | TARGET ${TestTarget} 227 | APPEND 228 | PROPERTY ParseAndAddCatchTests_TESTS "${CTestName}") 229 | set_property( 230 | SOURCE ${SourceFile} 231 | APPEND 232 | PROPERTY ParseAndAddCatchTests_TESTS "${CTestName}") 233 | endif() 234 | 235 | 236 | endforeach() 237 | endfunction() 238 | 239 | # entry point 240 | function(ParseAndAddCatchTests TestTarget) 241 | ParseAndAddCatchTests_PrintDebugMessage("Started parsing ${TestTarget}") 242 | get_target_property(SourceFiles ${TestTarget} SOURCES) 243 | ParseAndAddCatchTests_PrintDebugMessage("Found the following sources: ${SourceFiles}") 244 | foreach(SourceFile ${SourceFiles}) 245 | ParseAndAddCatchTests_ParseFile(${SourceFile} ${TestTarget}) 246 | endforeach() 247 | ParseAndAddCatchTests_PrintDebugMessage("Finished parsing ${TestTarget}") 248 | endfunction() 249 | -------------------------------------------------------------------------------- /src/cs_rcu_list.h: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Ansel Sermersheim 4 | * 5 | * This file is part of CsLibGuarded. 6 | * 7 | * CsLibGuarded is free software which is released under the BSD 2-Clause license. 8 | * For license details refer to the LICENSE provided with this project. 9 | * 10 | * CsLibGuarded is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | * 14 | * https://opensource.org/licenses/BSD-2-Clause 15 | * 16 | ***********************************************************************/ 17 | 18 | #ifndef CSLIBGUARDED_RCU_LIST_H 19 | #define CSLIBGUARDED_RCU_LIST_H 20 | 21 | #include "cs_rcu_guarded.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | namespace libguarded 30 | { 31 | 32 | /** 33 | \headerfile cs_rcu_list.h 34 | 35 | This templated class implements a linked list which is maintained 36 | using the RCU algorithm. Only one thread at a time may modify the 37 | linked list, but any number of threads may read 38 | simultaneously. Ongoing writes will not block readers. As a reader 39 | traverses the list while mutating operations are ongoing, the 40 | reader may see the old state or the new state. 41 | 42 | Since the RCU algorithm does not reap nodes until all readers who 43 | could have seen the node have completed, iterators are never 44 | invalidated by any list operation. 45 | 46 | This class will use std::mutex for the internal locking mechanism 47 | by default. Other classes which are useful for the mutex type are 48 | std::recursive_mutex, std::timed_mutex, and 49 | std::recursive_timed_mutex. 50 | */ 51 | template > 52 | class rcu_list 53 | { 54 | public: 55 | using value_type = T; 56 | using allocator_type = Alloc; 57 | using size_type = std::ptrdiff_t; 58 | using reference = value_type &; 59 | using const_reference = const value_type &; 60 | using pointer = typename std::allocator_traits::pointer; 61 | using const_pointer = typename std::allocator_traits::const_pointer; 62 | 63 | class iterator; 64 | class const_iterator; 65 | class reverse_iterator; 66 | class const_reverse_iterator; 67 | class end_iterator; 68 | class end_reverse_iterator; 69 | 70 | class rcu_guard; 71 | using rcu_write_guard = rcu_guard; 72 | using rcu_read_guard = rcu_guard; 73 | 74 | rcu_list(); 75 | explicit rcu_list(const Alloc &alloc); 76 | 77 | rcu_list(const rcu_list &) = delete; 78 | rcu_list(rcu_list &&) = delete; 79 | 80 | rcu_list &operator=(const rcu_list &) = delete; 81 | rcu_list &operator=(rcu_list &&) = delete; 82 | 83 | ~rcu_list(); 84 | 85 | [[nodiscard]] iterator begin(); 86 | [[nodiscard]] end_iterator end(); 87 | [[nodiscard]] const_iterator begin() const; 88 | [[nodiscard]] end_iterator end() const; 89 | [[nodiscard]] const_iterator cbegin() const; 90 | [[nodiscard]] end_iterator cend() const; 91 | 92 | void clear(); 93 | 94 | iterator insert(const_iterator pos, T value); 95 | iterator insert(const_iterator pos, size_type count, const T &value); 96 | 97 | template 98 | iterator insert(const_iterator pos, InputIter first, InputIter last); 99 | iterator insert(const_iterator pos, std::initializer_list ilist); 100 | 101 | template 102 | iterator emplace(const_iterator pos, Us &&... vs); 103 | 104 | void push_front(T value); 105 | void push_back(T value); 106 | 107 | template 108 | void emplace_front(Us &&... vs); 109 | 110 | template 111 | void emplace_back(Us &&... vs); 112 | 113 | iterator erase(const_iterator pos); 114 | 115 | private: 116 | struct node { 117 | // uncopyable, unmoveable 118 | node(const node &) = delete; 119 | node(node &&) = delete; 120 | 121 | node &operator=(const node &) = delete; 122 | node &operator=(node &&) = delete; 123 | 124 | template 125 | explicit node(Us &&... vs) 126 | : data(std::forward(vs)...) 127 | { 128 | } 129 | 130 | std::atomic next{nullptr}; 131 | std::atomic back{nullptr}; 132 | bool deleted{false}; 133 | T data; 134 | }; 135 | 136 | struct zombie_list_node { 137 | zombie_list_node(node *n) noexcept 138 | : zombie_node(n) 139 | { 140 | } 141 | 142 | zombie_list_node(rcu_guard *g) noexcept 143 | : owner(g) 144 | { 145 | } 146 | 147 | // uncopyable, unmoveable 148 | zombie_list_node(const zombie_list_node &) = delete; 149 | zombie_list_node(zombie_list_node &&) = delete; 150 | 151 | zombie_list_node &operator=(const zombie_list_node &) = delete; 152 | zombie_list_node &operator=(zombie_list_node &&) = delete; 153 | 154 | std::atomic next{nullptr}; 155 | std::atomic owner{nullptr}; 156 | node *zombie_node{nullptr}; 157 | }; 158 | 159 | using alloc_trait = std::allocator_traits; 160 | using node_alloc_t = typename alloc_trait::template rebind_alloc; 161 | using node_alloc_trait = std::allocator_traits; 162 | using zombie_alloc_t = typename alloc_trait::template rebind_alloc; 163 | using zombie_alloc_trait = std::allocator_traits; 164 | 165 | std::atomic m_head{nullptr}; 166 | std::atomic m_tail{nullptr}; 167 | 168 | mutable std::atomic m_zombie_head{nullptr}; 169 | 170 | M m_write_mutex; 171 | 172 | mutable node_alloc_t m_node_alloc; 173 | mutable zombie_alloc_t m_zombie_alloc; 174 | }; 175 | 176 | /*----------------------------------------*/ 177 | 178 | namespace detail 179 | { 180 | 181 | // allocator-aware deleter for unique_ptr 182 | template 183 | class deallocator 184 | { 185 | using allocator_type = Alloc; 186 | using allocator_traits = std::allocator_traits; 187 | using pointer = typename allocator_traits::pointer; 188 | 189 | allocator_type m_alloc; 190 | 191 | public: 192 | explicit deallocator(const allocator_type &alloc) noexcept 193 | : m_alloc(alloc) 194 | { 195 | } 196 | 197 | void operator()(pointer p) { 198 | if (p != nullptr) { 199 | allocator_traits::destroy(m_alloc, p); 200 | allocator_traits::deallocate(m_alloc, p, 1); 201 | } 202 | } 203 | }; 204 | 205 | // unique_ptr counterpart for std::allocate_shared() 206 | template 207 | std::unique_ptr> allocate_unique(Alloc &alloc, Args &&... args) 208 | { 209 | using allocator_traits = std::allocator_traits; 210 | 211 | auto p = allocator_traits::allocate(alloc, 1); 212 | 213 | try { 214 | allocator_traits::construct(alloc, p, std::forward(args)...); 215 | return {p, deallocator{alloc}}; 216 | 217 | } catch (...) { 218 | allocator_traits::deallocate(alloc, p, 1); 219 | throw; 220 | } 221 | } 222 | 223 | } // namespace detail 224 | 225 | /*----------------------------------------*/ 226 | 227 | template 228 | class rcu_list::rcu_guard 229 | { 230 | public: 231 | rcu_guard() = default; 232 | 233 | rcu_guard(const rcu_guard &other) = delete; 234 | rcu_guard &operator=(const rcu_guard &other) = delete; 235 | 236 | rcu_guard(rcu_guard &&other) { 237 | m_zombie = other.m_zombie; 238 | m_list = other.m_list; 239 | 240 | other.m_zombie = nullptr; 241 | other.m_list = nullptr; 242 | } 243 | 244 | rcu_guard &operator=(rcu_guard &&other) { 245 | m_zombie = other.m_zombie; 246 | m_list = other.m_list; 247 | 248 | other.m_zombie = nullptr; 249 | other.m_list = nullptr; 250 | } 251 | 252 | void rcu_read_lock(const rcu_list &list); 253 | void rcu_read_unlock(const rcu_list &list); 254 | 255 | void rcu_write_lock(rcu_list &list); 256 | void rcu_write_unlock(rcu_list &list); 257 | 258 | private: 259 | void unlock(); 260 | 261 | zombie_list_node *m_zombie; 262 | const rcu_list *m_list; 263 | }; 264 | 265 | template 266 | void rcu_list::rcu_guard::rcu_read_lock(const rcu_list &list) 267 | { 268 | m_list = &list; 269 | m_zombie = zombie_alloc_trait::allocate(list.m_zombie_alloc, 1); 270 | zombie_alloc_trait::construct(list.m_zombie_alloc, m_zombie, this); 271 | zombie_list_node *oldNext = list.m_zombie_head.load(std::memory_order_relaxed); 272 | 273 | do { 274 | m_zombie->next.store(oldNext, std::memory_order_relaxed); 275 | } while (!list.m_zombie_head.compare_exchange_weak(oldNext, m_zombie)); 276 | } 277 | 278 | template 279 | void rcu_list::rcu_guard::rcu_read_unlock(const rcu_list &) 280 | { 281 | unlock(); 282 | } 283 | 284 | template 285 | void rcu_list::rcu_guard::unlock() 286 | { 287 | zombie_list_node *cached_next = m_zombie->next.load(); 288 | zombie_list_node *n = cached_next; 289 | 290 | bool last = true; 291 | 292 | while (n) { 293 | if (n->owner.load() != nullptr) { 294 | last = false; 295 | break; 296 | } 297 | 298 | n = n->next.load(); 299 | } 300 | 301 | n = cached_next; 302 | 303 | if (last) { 304 | while (n) { 305 | node *deadNode = n->zombie_node; 306 | 307 | if (deadNode != nullptr) { 308 | node_alloc_trait::destroy(m_list->m_node_alloc, deadNode); 309 | node_alloc_trait::deallocate(m_list->m_node_alloc, deadNode, 1); 310 | } 311 | 312 | zombie_list_node *oldnode = n; 313 | n = n->next.load(); 314 | 315 | if (oldnode != nullptr) { 316 | zombie_alloc_trait::destroy(m_list->m_zombie_alloc, oldnode); 317 | zombie_alloc_trait::deallocate(m_list->m_zombie_alloc, oldnode, 1); 318 | } 319 | } 320 | 321 | m_zombie->next.store(n); 322 | } 323 | 324 | m_zombie->owner.store(nullptr); 325 | } 326 | 327 | template 328 | void rcu_list::rcu_guard::rcu_write_lock(rcu_list &list) 329 | { 330 | rcu_read_lock(list); 331 | list.m_write_mutex.lock(); 332 | } 333 | 334 | template 335 | void rcu_list::rcu_guard::rcu_write_unlock(rcu_list &list) 336 | { 337 | list.m_write_mutex.unlock(); 338 | rcu_read_unlock(list); 339 | } 340 | 341 | /*----------------------------------------*/ 342 | 343 | template 344 | class rcu_list::iterator 345 | { 346 | public: 347 | using iterator_category = std::forward_iterator_tag; 348 | using value_type = const T; 349 | using pointer = const T *; 350 | using reference = const T &; 351 | using difference_type = size_t; 352 | 353 | iterator() 354 | : m_current(nullptr) 355 | { 356 | } 357 | 358 | const T &operator*() const { 359 | return m_current->data; 360 | } 361 | 362 | const T *operator->() const { 363 | return &(m_current->data); 364 | } 365 | 366 | bool operator==(const end_iterator &) const { 367 | return m_current == nullptr; 368 | } 369 | 370 | bool operator!=(const end_iterator &) const { 371 | return m_current != nullptr; 372 | } 373 | 374 | iterator &operator++() { 375 | m_current = m_current->next.load(); 376 | return *this; 377 | } 378 | 379 | iterator &operator--() { 380 | m_current = m_current->prev.load(); 381 | return *this; 382 | } 383 | 384 | iterator operator++(int) { 385 | iterator old(*this); 386 | ++(*this); 387 | return old; 388 | } 389 | 390 | iterator operator--(int) { 391 | iterator old(*this); 392 | --(*this); 393 | return old; 394 | } 395 | 396 | private: 397 | friend rcu_list; 398 | friend rcu_list::const_iterator; 399 | 400 | explicit iterator(const typename rcu_list::const_iterator &it) 401 | : m_current(it.m_current) 402 | { 403 | } 404 | 405 | explicit iterator(node *n) 406 | : m_current(n) 407 | { 408 | } 409 | 410 | node *m_current; 411 | }; 412 | 413 | /*----------------------------------------*/ 414 | 415 | template 416 | class rcu_list::const_iterator 417 | { 418 | public: 419 | using iterator_category = std::forward_iterator_tag; 420 | using value_type = const T; 421 | using pointer = const T *; 422 | using reference = const T &; 423 | using difference_type = size_t; 424 | 425 | const_iterator() 426 | : m_current(nullptr) 427 | { 428 | } 429 | 430 | const_iterator(const typename rcu_list::iterator &it) 431 | : m_current(it.m_current) 432 | { 433 | } 434 | 435 | const T &operator*() const { 436 | return m_current->data; 437 | } 438 | 439 | const T *operator->() const { 440 | return &(m_current->data); 441 | } 442 | 443 | bool operator==(const end_iterator &) const { 444 | return m_current == nullptr; 445 | } 446 | 447 | bool operator!=(const end_iterator &) const { 448 | return m_current != nullptr; 449 | } 450 | 451 | const_iterator &operator++() { 452 | m_current = m_current->next.load(); 453 | return *this; 454 | } 455 | 456 | const_iterator &operator--() { 457 | m_current = m_current->prev.load(); 458 | return *this; 459 | } 460 | 461 | const_iterator operator++(int) { 462 | const_iterator old(*this); 463 | ++(*this); 464 | return old; 465 | } 466 | 467 | const_iterator operator--(int) { 468 | const_iterator old(*this); 469 | --(*this); 470 | return old; 471 | } 472 | 473 | private: 474 | friend rcu_list; 475 | 476 | explicit const_iterator(node *n) 477 | : m_current(n) 478 | { 479 | } 480 | 481 | node *m_current; 482 | }; 483 | 484 | /*----------------------------------------*/ 485 | 486 | template 487 | class rcu_list::end_iterator 488 | { 489 | public: 490 | bool operator==(iterator iter) const { 491 | return iter == *this; 492 | } 493 | 494 | bool operator!=(iterator iter) const { 495 | return iter != *this; 496 | } 497 | 498 | bool operator==(const_iterator iter) const { 499 | return iter == *this; 500 | } 501 | 502 | bool operator!=(const_iterator iter) const { 503 | return iter != *this; 504 | } 505 | }; 506 | 507 | /*----------------------------------------*/ 508 | 509 | template 510 | rcu_list::rcu_list() 511 | { 512 | m_head.store(nullptr); 513 | m_tail.store(nullptr); 514 | } 515 | 516 | template 517 | rcu_list::rcu_list(const Alloc &alloc) 518 | : m_node_alloc(alloc), m_zombie_alloc(alloc) 519 | { 520 | } 521 | 522 | template 523 | rcu_list::~rcu_list() 524 | { 525 | node *n = m_head.load(); 526 | 527 | while (n != nullptr) { 528 | node *current = n; 529 | n = n->next.load(); 530 | 531 | if (current != nullptr) { 532 | node_alloc_trait::destroy(m_node_alloc, current); 533 | node_alloc_trait::deallocate(m_node_alloc, current, 1); 534 | } 535 | } 536 | 537 | zombie_list_node *zn = m_zombie_head.load(); 538 | 539 | while (zn != nullptr && zn->owner.load() == nullptr) { 540 | zombie_list_node *current = zn; 541 | zn = zn->next.load(); 542 | 543 | if (current->zombie_node != nullptr) { 544 | node_alloc_trait::destroy(m_node_alloc, current->zombie_node); 545 | node_alloc_trait::deallocate(m_node_alloc, current->zombie_node, 1); 546 | } 547 | 548 | if (current != nullptr) { 549 | zombie_alloc_trait::destroy(m_zombie_alloc, current); 550 | zombie_alloc_trait::deallocate(m_zombie_alloc, current, 1); 551 | } 552 | } 553 | } 554 | 555 | template 556 | auto rcu_list::begin() -> iterator 557 | { 558 | return iterator(m_head.load()); 559 | } 560 | 561 | template 562 | auto rcu_list::end() -> end_iterator 563 | { 564 | return end_iterator(); 565 | } 566 | 567 | template 568 | auto rcu_list::begin() const -> const_iterator 569 | { 570 | return const_iterator(m_head.load()); 571 | } 572 | 573 | template 574 | auto rcu_list::end() const -> end_iterator 575 | { 576 | return end_iterator(); 577 | } 578 | 579 | template 580 | template 581 | auto rcu_list::emplace(const_iterator iter, Us &&...vs) -> iterator 582 | { 583 | auto newNode = detail::allocate_unique(m_node_alloc, std::forward(vs)...); 584 | 585 | node *oldHead = m_head.load(); 586 | node *oldTail = m_tail.load(); 587 | 588 | if (oldHead == nullptr) { 589 | // inserting into an empty list 590 | m_head.store(newNode.get()); 591 | m_tail.store(newNode.get()); 592 | 593 | } else if (oldHead == iter.m_current) { 594 | // inserting at the beginning of a non-empty list 595 | newNode->next.store(oldHead); 596 | oldHead->back.store(newNode.get()); 597 | m_head.store(newNode.get()); 598 | 599 | } else if (oldTail == iter.m_current) { 600 | // inserting at the end of a non-empty list 601 | newNode->back.store(oldTail); 602 | oldTail->next.store(newNode.get()); 603 | m_tail.store(newNode.get()); 604 | 605 | } else { 606 | // inserting in the middle of a non-empty list 607 | node *oldBack = iter.m_current->back.load(); 608 | 609 | newNode->next.store(iter.m_current); 610 | newNode->back.store(oldBack); 611 | iter.m_current->back.store(newNode.get()); 612 | 613 | if (oldBack != nullptr) { 614 | oldBack->next.store(newNode.get()); 615 | } 616 | } 617 | 618 | return iterator(newNode.release()); 619 | } 620 | 621 | template 622 | void rcu_list::push_front(T data) 623 | { 624 | auto newNode = detail::allocate_unique(m_node_alloc, std::move(data)); 625 | 626 | node *oldHead = m_head.load(); 627 | 628 | if (oldHead == nullptr) { 629 | m_head.store(newNode.get()); 630 | m_tail.store(newNode.release()); 631 | } else { 632 | newNode->next.store(oldHead); 633 | oldHead->back.store(newNode.get()); 634 | m_head.store(newNode.release()); 635 | } 636 | } 637 | 638 | template 639 | template 640 | void rcu_list::emplace_front(Us &&... vs) 641 | { 642 | auto newNode = detail::allocate_unique(m_node_alloc, std::forward(vs)...); 643 | 644 | node *oldHead = m_head.load(); 645 | 646 | if (oldHead == nullptr) { 647 | m_head.store(newNode.get()); 648 | m_tail.store(newNode.release()); 649 | } else { 650 | newNode->next.store(oldHead); 651 | oldHead->back.store(newNode.get()); 652 | m_head.store(newNode.release()); 653 | } 654 | } 655 | 656 | template 657 | void rcu_list::push_back(T data) 658 | { 659 | auto newNode = detail::allocate_unique(m_node_alloc, std::move(data)); 660 | 661 | node *oldTail = m_tail.load(std::memory_order_relaxed); 662 | 663 | if (oldTail == nullptr) { 664 | m_head.store(newNode.get()); 665 | m_tail.store(newNode.release()); 666 | } else { 667 | newNode->back.store(oldTail); 668 | oldTail->next.store(newNode.get()); 669 | m_tail.store(newNode.release()); 670 | } 671 | } 672 | 673 | template 674 | template 675 | void rcu_list::emplace_back(Us &&... vs) 676 | { 677 | auto newNode = detail::allocate_unique(m_node_alloc, std::forward(vs)...); 678 | 679 | node *oldTail = m_tail.load(std::memory_order_relaxed); 680 | 681 | if (oldTail == nullptr) { 682 | m_head.store(newNode.get()); 683 | m_tail.store(newNode.release()); 684 | } else { 685 | newNode->back.store(oldTail); 686 | oldTail->next.store(newNode.get()); 687 | m_tail.store(newNode.release()); 688 | } 689 | } 690 | 691 | template 692 | auto rcu_list::erase(const_iterator iter) -> iterator 693 | { 694 | // make sure the node has not already been marked for deletion 695 | node *oldNext = iter.m_current->next.load(); 696 | 697 | if (! iter.m_current->deleted) { 698 | iter.m_current->deleted = true; 699 | 700 | node *oldPrev = iter.m_current->back.load(); 701 | 702 | if (oldPrev) { 703 | oldPrev->next.store(oldNext); 704 | } else { 705 | // no previous node, this node was the head 706 | m_head.store(oldNext); 707 | } 708 | 709 | if (oldNext) { 710 | oldNext->back.store(oldPrev); 711 | } else { 712 | // no next node, this node was the tail 713 | m_tail.store(oldPrev); 714 | } 715 | 716 | auto newZombie = zombie_alloc_trait::allocate(m_zombie_alloc, 1); 717 | zombie_alloc_trait::construct(m_zombie_alloc, newZombie, iter.m_current); 718 | 719 | zombie_list_node *oldZombie = m_zombie_head.load(); 720 | 721 | do { 722 | newZombie->next = oldZombie; 723 | } while (! m_zombie_head.compare_exchange_weak(oldZombie, newZombie)); 724 | } 725 | 726 | return iterator(oldNext); 727 | } 728 | 729 | template 730 | using SharedList = rcu_guarded>; 731 | 732 | } // namespace libguarded 733 | 734 | #endif 735 | --------------------------------------------------------------------------------