From 3c8b70220cc653ef60bbba5b1cdf1bae4c4747de Mon Sep 17 00:00:00 2001 From: KRM7 <70973547+KRM7@users.noreply.github.com> Date: Wed, 6 Sep 2023 21:46:14 +0200 Subject: [PATCH] temp temp2 --- .github/workflows/sanitizers.yml | 2 +- .tsan-supressions | 13 +------ CMakeLists.txt | 1 + src/algorithm/nd_sort.cpp | 11 +++++- src/algorithm/nsga3.cpp | 6 ++- src/algorithm/reference_lines.cpp | 9 ++--- src/core/ga_base.impl.hpp | 28 +++++++++----- src/core/ga_info.cpp | 6 +-- src/core/ga_info.hpp | 5 ++- src/metrics/pop_stats.cpp | 6 ++- src/population/population.hpp | 6 ++- src/utility/atomic.hpp | 64 +++++++++++++++++++++++++++++++ src/utility/rcu.hpp | 28 ++++++++------ src/utility/rng.hpp | 6 ++- src/utility/shared_spinlock.hpp | 63 ++++++++++++++++++++++++++++++ src/utility/spinlock.hpp | 40 +++++++++++++++++++ src/utility/utility.hpp | 11 +++--- test/tsan/CMakeLists.txt | 15 ++++++++ test/tsan/tsan.cpp | 36 +++++++++++++++++ 19 files changed, 299 insertions(+), 57 deletions(-) create mode 100644 src/utility/atomic.hpp create mode 100644 src/utility/shared_spinlock.hpp create mode 100644 src/utility/spinlock.hpp create mode 100644 test/tsan/CMakeLists.txt create mode 100644 test/tsan/tsan.cpp diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index d92e1e44..acaafa87 100644 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - sanitizer: [ address, undefined ] + sanitizer: [ address, undefined, thread ] include: - cxx: clang++-15 pkgs: clang-15 llvm-15 libtbb-dev diff --git a/.tsan-supressions b/.tsan-supressions index f312e500..1a873fc1 100644 --- a/.tsan-supressions +++ b/.tsan-supressions @@ -1,7 +1,3 @@ -# Suppress warnings from Intel TBB -# (libstdc++ uses TBB to implement the parallel std algorithms) - -# clang race:^tbb::detail::d1::node::node race:^tbb::detail::d1::tree_node::tree_node @@ -14,10 +10,5 @@ race:tbb::detail::d1::small_object_allocator::delete_object race:tbb::detail::d1::dynamic_grainsize_mode<*>::check_being_stolen -race:^tbb::detail::d1::start_for<*>::finalize - -# This supression is just supposed to be the first one, but the function name isnt always displayed properly in the stack trace -race:^tbb::detail::d1::start_for<*>::offer_work -race:tbb::detail::d1::auto_partitioner const>::offer_work(tbb::detail::d0::split&, tbb::detail::d1::execution_data&) - -race:^tbb::detail::r1::isolate_within_arena \ No newline at end of file +race:tbb::detail::d1::start_for<*>::finalize +race:tbb::detail::d1::start_for<*>::offer_work_impl diff --git a/CMakeLists.txt b/CMakeLists.txt index 758f993e..dabcb6e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -185,6 +185,7 @@ if(GAPP_BUILD_TESTS AND BUILD_TESTING AND PROJECT_IS_TOP_LEVEL) add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/test/unit") add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/test/integration") add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/test/misc") + add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/test/tsan") endif() if(GAPP_BUILD_BENCHMARKS AND BUILD_TESTING AND PROJECT_IS_TOP_LEVEL) diff --git a/src/algorithm/nd_sort.cpp b/src/algorithm/nd_sort.cpp index 3f23dea0..42f29411 100644 --- a/src/algorithm/nd_sort.cpp +++ b/src/algorithm/nd_sort.cpp @@ -188,8 +188,10 @@ namespace gapp::algorithm::dtl const size_t popsize = std::distance(first, last); DominanceMatrix dmat(popsize, popsize /*, MAXIMAL */); - std::for_each(GAPP_EXEC_UNSEQ, detail::iota_iterator(0_sz), detail::iota_iterator(first->size()), [&](size_t obj) + GAPP_BARRIER(); + std::for_each(GAPP_EXEC, detail::iota_iterator(0_sz), detail::iota_iterator(first->size()), [&](size_t obj) { + GAPP_BARRIER(); FitnessVector fvec(popsize); std::transform(first, last, fvec.begin(), detail::element_at(obj)); @@ -210,10 +212,13 @@ namespace gapp::algorithm::dtl } }); }); + GAPP_BARRIER(); }); + GAPP_BARRIER(); - std::for_each(GAPP_EXEC_UNSEQ, detail::iota_iterator(0_sz), detail::iota_iterator(popsize), [&](size_t row) noexcept + std::for_each(GAPP_EXEC, detail::iota_iterator(0_sz), detail::iota_iterator(popsize), [&](size_t row) noexcept { + GAPP_BARRIER(); dmat(row, row).store(NONMAXIMAL, std::memory_order_relaxed); // diagonal is all nonmax for (size_t col = row + 1; col < popsize; col++) @@ -224,7 +229,9 @@ namespace gapp::algorithm::dtl dmat(col, row).store(NONMAXIMAL, std::memory_order_relaxed); } } + GAPP_BARRIER(); }); + GAPP_BARRIER(); return dmat; } diff --git a/src/algorithm/nsga3.cpp b/src/algorithm/nsga3.cpp index d1f35e03..510d02c9 100644 --- a/src/algorithm/nsga3.cpp +++ b/src/algorithm/nsga3.cpp @@ -239,8 +239,10 @@ namespace gapp::algorithm sol_info_.resize(last - first); - std::for_each(GAPP_EXEC_UNSEQ, pfirst, plast, [&](const FrontInfo& sol) + GAPP_BARRIER(); + std::for_each(GAPP_EXEC, pfirst, plast, [&](const FrontInfo& sol) { + GAPP_BARRIER(); const FitnessVector fnorm = normalizeFitnessVec(first[sol.idx], ideal_point_, nadir_point_); auto idistance = [&](const auto& line) { return std::inner_product(fnorm.begin(), fnorm.end(), line.begin(), 0.0); }; @@ -249,7 +251,9 @@ namespace gapp::algorithm sol_info_[sol.idx].ref_idx = std::distance(ref_lines_.begin(), closest); sol_info_[sol.idx].ref_dist = math::perpendicularDistanceSq(*closest, fnorm); + GAPP_BARRIER(); }); + GAPP_BARRIER(); } inline bool NSGA3::Impl::nichedCompare(size_t lhs, size_t rhs) const noexcept diff --git a/src/algorithm/reference_lines.cpp b/src/algorithm/reference_lines.cpp index d4e571c4..43f2ef93 100644 --- a/src/algorithm/reference_lines.cpp +++ b/src/algorithm/reference_lines.cpp @@ -183,12 +183,11 @@ namespace gapp::algorithm::reflines min_distances.pop_back(); /* Calc the distance of each candidate to the closest ref point. */ - std::transform(GAPP_EXEC_UNSEQ, candidate_points.begin(), candidate_points.end(), min_distances.begin(), min_distances.begin(), - [&](const Point& candidate, double current_min) noexcept + for (size_t i = 0; i < candidate_points.size(); i++) { - const double dist = math::euclideanDistanceSq(candidate, points.back()); - return std::min(current_min, dist); - }); + double dist = math::euclideanDistanceSq(candidate_points[i], points.back()); + min_distances[i] = std::min(min_distances[i], dist); + } } return points; diff --git a/src/core/ga_base.impl.hpp b/src/core/ga_base.impl.hpp index 2ea0dc53..86979022 100644 --- a/src/core/ga_base.impl.hpp +++ b/src/core/ga_base.impl.hpp @@ -327,7 +327,7 @@ namespace gapp /* Reset state in case solve() has already been called before. */ generation_cntr_ = 0; - num_fitness_evals_ = 0; + num_fitness_evals_->store(0, std::memory_order_release); solutions_.clear(); population_.clear(); @@ -339,7 +339,9 @@ namespace gapp /* Create and evaluate the initial population of the algorithm. */ num_objectives_ = findNumberOfObjectives(); population_ = generatePopulation(population_size_, std::move(initial_population)); - std::for_each(GAPP_EXEC_UNSEQ, population_.begin(), population_.end(), [this](Candidate& sol) { evaluate(sol); }); + GAPP_BARRIER(); + std::for_each(GAPP_EXEC, population_.begin(), population_.end(), [this](Candidate& sol) { GAPP_BARRIER(); evaluate(sol); GAPP_BARRIER(); }); + GAPP_BARRIER(); fitness_matrix_ = detail::toFitnessMatrix(population_); if (keep_all_optimal_sols_) solutions_ = detail::findParetoFront(population_); @@ -465,11 +467,10 @@ namespace gapp * is no point doing it again. */ if (!sol.is_evaluated || fitness_function_->dynamic()) { + num_fitness_evals_->fetch_add(1, std::memory_order_acq_rel); + sol.fitness = (*fitness_function_)(sol.chromosome); sol.is_evaluated = true; - - std::atomic_ref num_evals{ num_fitness_evals_ }; - num_evals.fetch_add(1_sz, std::memory_order_acq_rel); } GAPP_ASSERT(hasValidFitness(sol)); @@ -504,27 +505,34 @@ namespace gapp std::vector> child_pairs(num_children / 2); prepareSelections(); - std::generate(GAPP_EXEC_UNSEQ, child_pairs.begin(), child_pairs.end(), - [this] + GAPP_BARRIER(); + std::for_each(GAPP_EXEC, child_pairs.begin(), child_pairs.end(), + [this](CandidatePair& children) { + GAPP_BARRIER(); const auto& parent1 = select(); const auto& parent2 = select(); - - return crossover(parent1, parent2); + children = crossover(parent1, parent2); + GAPP_BARRIER(); }); + GAPP_BARRIER(); auto children = detail::flatten(std::move(child_pairs)); /* If the population size is odd, one too many child candidates were generated by the crossovers. */ if (children.size() > population_size_) children.pop_back(); - std::for_each(GAPP_EXEC_UNSEQ, children.begin(), children.end(), + GAPP_BARRIER(); + std::for_each(GAPP_EXEC, children.begin(), children.end(), [this](Candidate& child) { + GAPP_BARRIER(); mutate(child); repair(child); evaluate(child); + GAPP_BARRIER(); }); + GAPP_BARRIER(); updatePopulation(std::move(children)); if (keep_all_optimal_sols_) updateOptimalSolutions(solutions_, population_); diff --git a/src/core/ga_info.cpp b/src/core/ga_info.cpp index 0f40a3e9..8bf57252 100644 --- a/src/core/ga_info.cpp +++ b/src/core/ga_info.cpp @@ -4,7 +4,6 @@ #include "../algorithm/single_objective.hpp" #include "../stop_condition/stop_condition.hpp" #include "../utility/utility.hpp" -#include #include #include @@ -13,7 +12,7 @@ namespace gapp GaInfo::GaInfo(GaInfo&&) noexcept = default; GaInfo& GaInfo::operator=(GaInfo&&) noexcept = default; - GaInfo::~GaInfo() = default; + GaInfo::~GaInfo() noexcept = default; GaInfo::GaInfo(Positive population_size, std::unique_ptr algorithm, std::unique_ptr stop_condition) noexcept : @@ -26,8 +25,7 @@ namespace gapp size_t GaInfo::num_fitness_evals() const noexcept { - std::atomic_ref num_fitness_evals{ num_fitness_evals_ }; - return num_fitness_evals.load(std::memory_order_acquire); + return num_fitness_evals_->load(std::memory_order_acquire); } void GaInfo::algorithm(std::unique_ptr f) diff --git a/src/core/ga_info.hpp b/src/core/ga_info.hpp index 6d059f2b..8708c1e5 100644 --- a/src/core/ga_info.hpp +++ b/src/core/ga_info.hpp @@ -5,6 +5,7 @@ #include "../population/population.hpp" #include "../utility/bounded_value.hpp" +#include "../utility/atomic.hpp" #include "../utility/utility.hpp" #include "../metrics/metric_set.hpp" #include @@ -341,7 +342,7 @@ namespace gapp GaInfo& operator=(const GaInfo&) = delete; /** Destructor. */ - virtual ~GaInfo(); + virtual ~GaInfo() noexcept; protected: @@ -359,7 +360,7 @@ namespace gapp Positive max_gen_ = 500; size_t num_objectives_ = 0; size_t generation_cntr_ = 0; - size_t num_fitness_evals_ = 0; + detail::atomic num_fitness_evals_ = 0; bool keep_all_optimal_sols_ = false; bool use_default_algorithm_ = false; diff --git a/src/metrics/pop_stats.cpp b/src/metrics/pop_stats.cpp index 284a3e3e..ddd46e1f 100644 --- a/src/metrics/pop_stats.cpp +++ b/src/metrics/pop_stats.cpp @@ -154,15 +154,19 @@ namespace gapp::detail const FitnessMatrix front = uniqueSortedParetoFront(fmat); std::atomic hypervolume = 0.0; - std::for_each(GAPP_EXEC_UNSEQ, detail::iota_iterator(0_sz), detail::iota_iterator(front.size()), [&](size_t idx) + GAPP_BARRIER(); + std::for_each(GAPP_EXEC, detail::iota_iterator(0_sz), detail::iota_iterator(front.size()), [&](size_t idx) { + GAPP_BARRIER(); const auto point = front[idx]; const FitnessMatrix rest = { front.begin() + idx + 1, front.end() }; const double exclusive_hypervolume = exclusiveHypervolume(point, rest, ref_point); hypervolume.fetch_add(exclusive_hypervolume, std::memory_order_acq_rel); + GAPP_BARRIER(); }); + GAPP_BARRIER(); return hypervolume.load(std::memory_order_acquire); } diff --git a/src/population/population.hpp b/src/population/population.hpp index 6d0ccf4b..afc2a6b9 100644 --- a/src/population/population.hpp +++ b/src/population/population.hpp @@ -106,8 +106,10 @@ namespace gapp::detail std::vector lhs_state(lhs.size()); std::vector> rhs_state(rhs.size()); - std::for_each(GAPP_EXEC_UNSEQ, iota_iterator(0_sz), iota_iterator(lhs.size()), [&](size_t i) noexcept + GAPP_BARRIER(); + std::for_each(GAPP_EXEC, iota_iterator(0_sz), iota_iterator(lhs.size()), [&](size_t i) noexcept { + GAPP_BARRIER(); for (size_t j = 0; j < rhs.size(); j++) { const Dominance rhs_state_j = rhs_state[j].load(std::memory_order_acquire); @@ -145,7 +147,9 @@ namespace gapp::detail } // comp == 0 --> both are OPTIMAL or DOMINATED, can't know } + GAPP_BARRIER(); }); + GAPP_BARRIER(); Candidates optimal_solutions; optimal_solutions.reserve(lhs.size() + rhs.size()); diff --git a/src/utility/atomic.hpp b/src/utility/atomic.hpp new file mode 100644 index 00000000..b3881d9d --- /dev/null +++ b/src/utility/atomic.hpp @@ -0,0 +1,64 @@ +/* Copyright (c) 2023 Krisztián Rugási. Subject to the MIT License. */ + +#ifndef GA_UTILITY_ATOMIC_HPP +#define GA_UTILITY_ATOMIC_HPP + +#include +#include +#include + +namespace gapp::detail +{ + /* + * This is a simple wrapper class around std::atomic with atomic + * initialization, in order to prevent data races between the + * initialization of the variable and later accesses to it. + * + * The wrapper also adds move operators for convenience. + */ + template + class atomic + { + public: + atomic(std::memory_order order = std::memory_order_seq_cst) noexcept + { + data_.store(T{}, order); + } + + atomic(T value, std::memory_order order = std::memory_order_seq_cst) noexcept + { + data_.store(std::move(value), order); + } + + atomic(atomic&& other) noexcept + { + data_.store(other->load()); + } + + atomic& operator=(T value) noexcept + { + data_.store(std::move(value)); + return *this; + } + + atomic& operator=(atomic&& other) noexcept + { + data_.store(other->load()); + return *this; + } + + ~atomic() noexcept = default; // maybe needs release? + + std::atomic& operator*() noexcept { return data_; } + std::atomic* operator->() noexcept { return std::addressof(data_); } + const std::atomic& operator*() const noexcept { return data_; } + const std::atomic* operator->() const noexcept { return std::addressof(data_); } + + private: + std::atomic data_; + }; + + +} // namespace gapp::detail + +#endif // !GA_UTILITY_ATOMIC_HPP diff --git a/src/utility/rcu.hpp b/src/utility/rcu.hpp index 1dc64130..134b0299 100644 --- a/src/utility/rcu.hpp +++ b/src/utility/rcu.hpp @@ -4,11 +4,11 @@ #define GA_UTILITY_RCU_HPP #include "utility.hpp" +#include "shared_spinlock.hpp" #include #include #include #include -#include #include #include @@ -37,11 +37,11 @@ namespace gapp::detail uint64_t target = current + 1; writer_epoch.compare_exchange_strong(current, target, std::memory_order_acq_rel); - std::shared_lock _{ reader_list_mtx }; + std::shared_lock _{ tls_readers.lock }; - for (const registered_reader* reader_ : reader_list) + for (const registered_reader* tls_reader : tls_readers.list) { - while (reader_->epoch.load(std::memory_order_acquire) < target) { GAPP_PAUSE(); } + while (tls_reader->epoch.load(std::memory_order_acquire) < target) GAPP_PAUSE(); } } @@ -50,25 +50,29 @@ namespace gapp::detail { registered_reader() noexcept { - std::unique_lock _{ reader_list_mtx }; - reader_list.push_back(this); + std::unique_lock _{ tls_readers.lock }; + tls_readers.list.push_back(this); } ~registered_reader() noexcept { - std::unique_lock _{ reader_list_mtx }; - std::erase(reader_list, this); + std::unique_lock _{ tls_readers.lock }; + std::erase(tls_readers.list, this); } std::atomic epoch = NOT_READING; }; - inline static constexpr uint64_t NOT_READING = std::numeric_limits::max(); + struct tls_reader_list + { + detail::shared_spinlock lock; + std::vector list; + }; - GAPP_API inline static std::vector reader_list; - GAPP_API inline static std::shared_mutex reader_list_mtx; + inline static constexpr uint64_t NOT_READING = std::numeric_limits::max(); - alignas(128) GAPP_API inline static constinit std::atomic writer_epoch = 0; + GAPP_API inline static tls_reader_list& tls_readers = *new tls_reader_list; + GAPP_API inline static constinit std::atomic writer_epoch = 0; alignas(128) inline static thread_local registered_reader reader; }; diff --git a/src/utility/rng.hpp b/src/utility/rng.hpp index dabcf5e9..63f4a555 100644 --- a/src/utility/rng.hpp +++ b/src/utility/rng.hpp @@ -221,8 +221,10 @@ namespace gapp::rng ~RegisteredGenerator() noexcept { - std::scoped_lock _{ generator_list_mtx_ }; - std::erase(generator_list, std::addressof(instance)); + //std::scoped_lock _{ generator_list_mtx_ }; + //std::erase(generator_list, std::addressof(instance)); + // TLS: use after free (std::erase after generator_list was destroyed) + // the issue is the same thing as in the RCU implementation } Generator instance{ 0 }; diff --git a/src/utility/shared_spinlock.hpp b/src/utility/shared_spinlock.hpp new file mode 100644 index 00000000..14d8216d --- /dev/null +++ b/src/utility/shared_spinlock.hpp @@ -0,0 +1,63 @@ +/* Copyright (c) 2023 Krisztián Rugási. Subject to the MIT License. */ + +#ifndef GA_UTILITY_SHARED_SPINLOCK_HPP +#define GA_UTILITY_SHARED_SPINLOCK_HPP + +#include "utility.hpp" +#include "spinlock.hpp" +#include +#include + +namespace gapp::detail +{ + class shared_spinlock + { + public: + void lock() noexcept + { + lock_.lock(); + while (read_cnt_.load(std::memory_order_relaxed)) GAPP_PAUSE(); + std::atomic_thread_fence(std::memory_order_acquire); + } + + bool try_lock() noexcept + { + return lock_.try_lock() && !read_cnt_.load(std::memory_order_acquire); + } + + void unlock() noexcept + { + lock_.unlock(); + } + + void lock_shared() noexcept + { + lock_.lock(); + read_cnt_.fetch_add(1, std::memory_order_relaxed); + lock_.unlock(); + } + + bool try_lock_shared() noexcept + { + if (lock_.try_lock()) + { + read_cnt_.fetch_add(1, std::memory_order_relaxed); + lock_.unlock(); + return true; + } + return false; + } + + void unlock_shared() noexcept + { + read_cnt_.fetch_sub(1, std::memory_order_release); + } + + private: + std::atomic read_cnt_; + detail::spinlock lock_; + }; + +} // namespace gapp::detail + +#endif // !GA_UTILITY_SHARED_SPINLOCK_HPP diff --git a/src/utility/spinlock.hpp b/src/utility/spinlock.hpp new file mode 100644 index 00000000..4c7e58dd --- /dev/null +++ b/src/utility/spinlock.hpp @@ -0,0 +1,40 @@ +/* Copyright (c) 2023 Krisztián Rugási. Subject to the MIT License. */ + +#ifndef GA_UTILITY_SPINLOCK_HPP +#define GA_UTILITY_SPINLOCK_HPP + +#include "utility.hpp" +#include + +namespace gapp::detail +{ + class spinlock + { + public: + void lock() noexcept + { + while (true) + { + if (!locked_.test_and_set(std::memory_order_acquire)) break; + while (locked_.test(std::memory_order_relaxed)) GAPP_PAUSE(); + } + } + + bool try_lock() noexcept + { + return !locked_.test(std::memory_order_relaxed) && + !locked_.test_and_set(std::memory_order_acquire); + } + + void unlock() noexcept + { + locked_.clear(std::memory_order_release); + } + + private: + std::atomic_flag locked_; + }; + +} // namespace gapp::detail + +#endif // !GA_UTILITY_SPINLOCK_HPP diff --git a/src/utility/utility.hpp b/src/utility/utility.hpp index fc0f1662..ca8aa1fe 100644 --- a/src/utility/utility.hpp +++ b/src/utility/utility.hpp @@ -106,13 +106,14 @@ #endif -#ifndef GAPP_EXCUTION_UNSEQ -# define GAPP_EXEC_UNSEQ std::execution::par_unseq +#ifndef GAPP_EXEC +# define GAPP_EXEC std::execution::par #endif -#ifndef GAPP_EXEC_SEQ -# define GAPP_EXEC_SEQ std::execution::par -#endif + +#include +inline std::atomic_bool global_barrier; +#define GAPP_BARRIER() std::ignore = global_barrier.exchange(true, std::memory_order_seq_cst) namespace gapp diff --git a/test/tsan/CMakeLists.txt b/test/tsan/CMakeLists.txt new file mode 100644 index 00000000..f20746d8 --- /dev/null +++ b/test/tsan/CMakeLists.txt @@ -0,0 +1,15 @@ +find_package(Catch2 3 REQUIRED) + +if(MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd4388") +endif() + +file(GLOB_RECURSE TEST_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") +file(GLOB_RECURSE TEST_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.hpp") + +add_executable(tsan_tests ${TEST_SOURCES} ${TEST_HEADERS}) + +target_link_libraries(tsan_tests PRIVATE Catch2::Catch2WithMain gapp) + +include(Catch) +catch_discover_tests(tsan_tests) diff --git a/test/tsan/tsan.cpp b/test/tsan/tsan.cpp new file mode 100644 index 00000000..c7a8ebd7 --- /dev/null +++ b/test/tsan/tsan.cpp @@ -0,0 +1,36 @@ +#include +#include +#include + +std::vector numbers = { 0, 1, 2, 3 }; +std::mutex mtx; + +TEST_CASE("empty", "[tsan]") +{ + std::for_each(std::execution::par, numbers.begin(), numbers.end(), [](const auto&) {}); +} + +TEST_CASE("simple", "[tsan]") +{ + { std::scoped_lock _{ mtx }; } + std::for_each(std::execution::par, numbers.begin(), numbers.end(), [](auto& n) { + std::scoped_lock _{ mtx }; ++n; + }); + { std::scoped_lock _{ mtx }; } // TSAN: race with numbers vector destructor +} + +//TEST_CASE("race1", "[tsan]") +//{ +// std::vector numbers2{ 0, 1, 2, 3 }; +// std::for_each(std::execution::par, numbers2.begin(), numbers2.end(), [](auto& n) { +// ++n; +// }); +//} + +//TEST_CASE("race2", "[tsan]") +//{ +// std::vector numbers2{ 0, 1, 2, 3 }; +// std::for_each(std::execution::par, numbers2.begin(), numbers2.end(), [](auto& n) { +// numbers[0] += n; +// }); +//}