From c9ba9bc189e545d355578e98edeea44e61ca45b7 Mon Sep 17 00:00:00 2001 From: Matthew Fidler Date: Tue, 27 Oct 2026 09:27:22 -0500 Subject: [PATCH] Throttle OpenMP stress tests to 2 threads for CRAN The trust stress test asked for 8 threads; under CRAN's OMP_THREAD_LIMIT=2 libomp warned 'Cannot form a team with 8 threads'. Request 2 threads and cap both stress helpers at omp_get_thread_limit() (#3). --- NEWS.md | 4 ++++ src/solver_test_objfuns.cpp | 3 ++- src/trust_test_objfuns.cpp | 5 ++++- tests/testthat/test-c-objfun-threadsafety.R | 6 +++++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index 922c477..75ce4b6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,10 @@ is now stored by value and re-thrown afresh instead of going through `std::exception_ptr` (#3). +* The OpenMP thread-safety tests use at most 2 threads and respect + `OMP_THREAD_LIMIT`, so CRAN no longer sees "OMP: Warning #96: Cannot + form a team" (#3). + # RcppTrust 0.2.0 * The package license is now GPL-2, because the BOBYQA/NEWUOA ports and diff --git a/src/solver_test_objfuns.cpp b/src/solver_test_objfuns.cpp index fe9c67e..58dae10 100644 --- a/src/solver_test_objfuns.cpp +++ b/src/solver_test_objfuns.cpp @@ -132,7 +132,8 @@ List solver_openmp_stress_test(std::string solver, int n, int nStarts, std::vector threadIds(nStarts, 0); #ifdef _OPENMP - int usedThreads = std::max(1, std::min(nThreadsRequested, omp_get_max_threads())); + int usedThreads = std::max(1, std::min({nThreadsRequested, omp_get_max_threads(), + omp_get_thread_limit()})); #pragma omp parallel for num_threads(usedThreads) schedule(static) #endif for (int i = 0; i < nStarts; i++) { diff --git a/src/trust_test_objfuns.cpp b/src/trust_test_objfuns.cpp index 5fa6323..d610a58 100644 --- a/src/trust_test_objfuns.cpp +++ b/src/trust_test_objfuns.cpp @@ -124,7 +124,10 @@ List trust_openmp_stress_test(int nStarts, int nThreadsRequested) { std::vector threadIds(nStarts, -1); #ifdef _OPENMP - int usedThreads = std::min(nThreadsRequested, omp_get_max_threads()); + // Also respect OMP_THREAD_LIMIT (CRAN allows 2 threads), so the runtime + // never has to shrink the team and warn. + int usedThreads = std::min({nThreadsRequested, omp_get_max_threads(), + omp_get_thread_limit()}); if (usedThreads < 1) usedThreads = 1; #pragma omp parallel for num_threads(usedThreads) schedule(static) #endif diff --git a/tests/testthat/test-c-objfun-threadsafety.R b/tests/testthat/test-c-objfun-threadsafety.R index 489a0c6..b870b24 100644 --- a/tests/testthat/test-c-objfun-threadsafety.R +++ b/tests/testthat/test-c-objfun-threadsafety.R @@ -6,7 +6,11 @@ # (not thread-safe) and keeps no shared mutable state across calls. test_that("trust_solve_c() gives identical results run sequentially vs. across OpenMP threads", { - res <- RcppTrust:::trust_openmp_stress_test(nStarts = 64L, nThreadsRequested = 8L) + # Two threads: the most CRAN allows, and enough to exercise concurrency. + res <- RcppTrust:::trust_openmp_stress_test( + nStarts = 64L, + nThreadsRequested = 2L + ) expect_equal(res$maxArgDiff, 0) expect_equal(res$maxValDiff, 0)