Skip to content

Commit d786291

Browse files
committed
test: add threadsafe execute test and sanitizer CI modes
Add threadsafe_execute regression test verifying concurrent execute() calls on the same plan produce correct results. Add sanitizer mode selection via FINUFFT_USE_SANITIZERS=OFF|ON|MEMSAN|TSAN, and extend the sanitizer GitHub workflow to run a focused Linux TSAN job.
1 parent 560bce9 commit d786291

File tree

6 files changed

+117
-6
lines changed

6 files changed

+117
-6
lines changed

.github/workflows/cmake_sanitizers.yml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ jobs:
5555
fail-fast: false
5656
matrix:
5757
include:
58-
- { os: ubuntu-22.04, toolchain: gcc-13 }
59-
- { os: macos-14, toolchain: llvm }
58+
- { os: ubuntu-22.04, toolchain: gcc-13, sanitizer: ON }
59+
- { os: ubuntu-22.04, toolchain: gcc-13, sanitizer: TSAN }
60+
- { os: macos-14, toolchain: llvm, sanitizer: ON }
6061

6162
steps:
6263
- name: Show CPU info (Linux)
@@ -136,15 +137,21 @@ jobs:
136137
for arch in "${arch_flags[@]}"; do
137138
rm -rf $build_dir
138139
140+
ctest_args=(--output-on-failure -j)
141+
# Keep the TSAN job focused on the concurrency-sensitive coverage.
142+
if [[ "${{ matrix.sanitizer }}" == "TSAN" ]]; then
143+
ctest_args=(--output-on-failure -R '^(run_testutils|run_threadsafe_execute)$')
144+
fi
145+
139146
cmake -E make_directory "$build_dir"
140147
cmake -S . -B "$build_dir" \
141148
-DCMAKE_BUILD_TYPE=$build_type \
142149
-DFINUFFT_ARCH_FLAGS="$arch" \
143150
-DFINUFFT_BUILD_EXAMPLES=ON \
144151
-DFINUFFT_BUILD_TESTS=ON \
145152
-DFINUFFT_USE_DUCC0=ON \
146-
-DFINUFFT_USE_SANITIZERS=ON
153+
-DFINUFFT_ENABLE_SANITIZERS="${{ matrix.sanitizer }}"
147154
148155
cmake --build "$build_dir" --config "$build_type"
149-
ctest --test-dir "$build_dir" --output-on-failure -j
156+
ctest --test-dir "$build_dir" "${ctest_args[@]}"
150157
done

CHANGELOG

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ If not stated, FINUFFT is assumed (old cuFINUFFT <=1.3 is listed separately).
33

44
v2.6.0-dev
55

6+
* Added `threadsafe_execute` regression test verifying concurrent `execute()`
7+
calls on the same plan produce correct results. Added sanitizer mode selection
8+
via `FINUFFT_USE_SANITIZERS=OFF|ON|MEMSAN|TSAN`, and extended the sanitizer
9+
GitHub workflow to run a focused Linux TSAN job. (Barbone)
610
* SIMD-vectorized bin sort with parallel prefix sum: uint32_t bin counts,
711
ndims dispatch for vectorized coordinate binning, std::exclusive_scan for
812
parallel prefix sum of offsets, restored single-threaded variant as

CMakeLists.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,19 @@ option(FINUFFT_USE_CUDA "Whether to build CUDA accelerated FINUFFT library (libc
2626
option(FINUFFT_USE_DUCC0 "Whether to use DUCC0 (instead of FFTW) for CPU FFTs" OFF)
2727
option(FINUFFT_USE_IWYU "Set CXX_INCLUDE_WHAT_YOU_USE on target (checker-only)" OFF)
2828
option(FINUFFT_USE_OPENMP "Whether to use OpenMP for parallelization. If disabled, the finufft library will be single threaded. This does not affect the choice of FFTW library." ON)
29-
option(FINUFFT_USE_SANITIZERS "Whether to enable sanitizers, only effective for Debug configuration." OFF)
29+
set(
30+
FINUFFT_USE_SANITIZERS
31+
"OFF"
32+
CACHE STRING
33+
"Sanitizer mode for Debug/RelWithDebInfo builds. Supported values: OFF, ON, MEMSAN, TSAN. ON and MEMSAN both select the default address/undefined/bounds bundle."
34+
)
3035
# if FINUFFT_USE_DUCC0 is ON, the following options are ignored
3136
set(FINUFFT_FFTW_LIBRARIES "DEFAULT" CACHE STRING "Specify a custom FFTW library")
3237
set(FINUFFT_FFTW_SUFFIX "DEFAULT" CACHE STRING "Suffix for FFTW libraries (e.g. OpenMP, Threads etc.) defaults to empty string if OpenMP is disabled, else uses OpenMP. Ignored if DUCC0 is used.")
3338
# if FINUFFT_USE_CPU is OFF, the following options are ignored
3439
set(FINUFFT_ARCH_FLAGS "native" CACHE STRING "Compiler flags for specifying target architecture, defaults to -march=native")
3540
# sphinx tag (don't remove): @cmake_opts_end
41+
set_property(CACHE FINUFFT_USE_SANITIZERS PROPERTY STRINGS OFF ON MEMSAN TSAN)
3642
cmake_dependent_option(FINUFFT_ENABLE_INSTALL "Disable installation in the case of python builds" ON "NOT FINUFFT_BUILD_PYTHON" OFF)
3743
cmake_dependent_option(FINUFFT_STATIC_LINKING "Disable static libraries in the case of python builds" ON "NOT FINUFFT_BUILD_PYTHON" OFF)
3844
cmake_dependent_option(FINUFFT_SHARED_LINKING "Shared should be the opposite of static linking" ON "NOT FINUFFT_STATIC_LINKING" OFF)

cmake/toolchain.cmake

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,26 @@ endif()
9191

9292
# ---- Sanitizers ---------------------------------------------------------------
9393
set(FINUFFT_SANITIZER_FLAGS)
94-
if(FINUFFT_USE_SANITIZERS)
94+
string(TOUPPER "${FINUFFT_USE_SANITIZERS}" FINUFFT_USE_SANITIZERS_MODE)
95+
if(FINUFFT_USE_SANITIZERS_MODE STREQUAL "OFF")
96+
elseif(FINUFFT_USE_SANITIZERS_MODE STREQUAL "ON" OR FINUFFT_USE_SANITIZERS_MODE STREQUAL "MEMSAN")
9597
set(FINUFFT_SANITIZER_FLAGS
9698
-fsanitize=address
9799
-fsanitize=undefined
98100
-fsanitize=bounds-strict
99101
/fsanitize=address
100102
/RTC1
101103
)
104+
elseif(FINUFFT_USE_SANITIZERS_MODE STREQUAL "TSAN")
105+
set(FINUFFT_SANITIZER_FLAGS -fsanitize=thread)
106+
else()
107+
message(
108+
FATAL_ERROR
109+
"Unsupported FINUFFT_USE_SANITIZERS value '${FINUFFT_USE_SANITIZERS}'. Use one of: OFF, ON, MEMSAN, TSAN."
110+
)
111+
endif()
112+
113+
if(FINUFFT_SANITIZER_FLAGS)
102114
filter_supported_compiler_flags(FINUFFT_SANITIZER_FLAGS FINUFFT_SANITIZER_FLAGS)
103115
set(FINUFFT_SANITIZER_FLAGS $<$<CONFIG:Debug,RelWithDebInfo>:${FINUFFT_SANITIZER_FLAGS}>)
104116
endif()

test/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ target_compile_features(testutils PRIVATE cxx_std_17)
5656
finufft_link_test(testutils)
5757
add_test(NAME run_testutils COMMAND testutils WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
5858

59+
add_executable(threadsafe_execute threadsafe_execute.cpp)
60+
target_compile_features(threadsafe_execute PRIVATE cxx_std_17)
61+
finufft_link_test(threadsafe_execute)
62+
add_test(NAME run_threadsafe_execute COMMAND threadsafe_execute WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
63+
5964
if(NOT FINUFFT_USE_DUCC0 AND FINUFFT_USE_OPENMP)
6065
find_package(OpenMP COMPONENTS CXX REQUIRED)
6166
add_executable(fftw_lock_test fftw_lock_test.cpp)

test/threadsafe_execute.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#include <finufft.h>
2+
#include <finufft_common/constants.h>
3+
#include <finufft_opts.h>
4+
5+
#include <algorithm>
6+
#include <cmath>
7+
#include <complex>
8+
#include <cstdint>
9+
#include <cstdio>
10+
#include <thread>
11+
#include <vector>
12+
13+
#include "utils/dirft1d.hpp"
14+
#include "utils/norms.hpp"
15+
16+
int main() {
17+
constexpr int nthreads = 4;
18+
constexpr int nreps = 16;
19+
constexpr int M = 400;
20+
constexpr int64_t N1 = 2048;
21+
constexpr double tol = 1e-12;
22+
23+
finufft_opts opts;
24+
finufft_default_opts(&opts);
25+
opts.nthreads = 1; // crucial: parallelism is across concurrent plan executes
26+
opts.debug = 0;
27+
28+
std::vector<double> x(M);
29+
std::vector<std::complex<double>> c(M), ref(N1);
30+
for (int j = 0; j < M; ++j) {
31+
double t = static_cast<double>(j) / M;
32+
x[j] = -finufft::common::PI + 2.0 * finufft::common::PI * t;
33+
c[j] = std::complex<double>(0.5 * std::cos(13.0 * t) + 0.25 * std::sin(7.0 * t),
34+
0.75 * std::sin(11.0 * t) - 0.2 * std::cos(5.0 * t));
35+
}
36+
37+
int64_t Ns[3] = {N1, 1, 1};
38+
finufft_plan plan;
39+
int ier = finufft_makeplan(1, 1, Ns, +1, 1, tol, &plan, &opts);
40+
if (ier != 0) {
41+
std::fprintf(stderr, "finufft_makeplan failed: ier=%d\n", ier);
42+
return ier;
43+
}
44+
ier = finufft_setpts(plan, M, x.data(), nullptr, nullptr, 0, nullptr, nullptr, nullptr);
45+
if (ier != 0) {
46+
std::fprintf(stderr, "finufft_setpts failed: ier=%d\n", ier);
47+
finufft_destroy(plan);
48+
return ier;
49+
}
50+
51+
dirft1d1<int64_t>(M, x, c, +1, N1, ref);
52+
53+
std::vector<int> failures(nthreads, 0);
54+
55+
std::vector<std::thread> workers;
56+
workers.reserve(nthreads);
57+
for (int tid = 0; tid < nthreads; ++tid) {
58+
workers.emplace_back([&, tid]() {
59+
std::vector<std::complex<double>> out(N1);
60+
for (int rep = 0; rep < nreps; ++rep) {
61+
int local_ier = finufft_execute(plan, c.data(), out.data());
62+
double relerr = relerrtwonorm(N1, ref.data(), out.data());
63+
if (local_ier != 0 || relerr > 10.0 * tol) {
64+
failures[tid] = 1;
65+
std::fprintf(stderr, "thread %d rep %d failed: ier=%d relerr=%.3g\n", tid, rep,
66+
local_ier, relerr);
67+
return;
68+
}
69+
}
70+
});
71+
}
72+
73+
for (auto &worker : workers) worker.join();
74+
75+
finufft_destroy(plan);
76+
return *std::max_element(failures.begin(), failures.end());
77+
}

0 commit comments

Comments
 (0)