[llvm] [Support][Jobserver][Tests] Simplify default executor init and make (PR #165264)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Oct 27 08:16:31 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-support
Author: Yaxun (Sam) Liu (yxsamliu)
<details>
<summary>Changes</summary>
jobserver tests deterministic
- Replace call-once wrapper in Parallel.cpp with a function-local static default executor.
- Rework Jobserver tests for parallelFor/parallelSort to run in a fresh subprocess. The parent test spawns the current test binary with a gtest filter selecting a child test, ensuring the child process initializes the default executor after setting parallel::strategy = jobserver_concurrency() and after setting up a FIFO-backed jobserver proxy. This makes the tests reliable and independent from prior executor initialization in the combined SupportTests binary.
---
Full diff: https://github.com/llvm/llvm-project/pull/165264.diff
2 Files Affected:
- (modified) llvm/lib/Support/Parallel.cpp (+3-20)
- (modified) llvm/unittests/Support/JobserverTest.cpp (+63-15)
``````````diff
diff --git a/llvm/lib/Support/Parallel.cpp b/llvm/lib/Support/Parallel.cpp
index 8e0c724accb36..f382a174a492a 100644
--- a/llvm/lib/Support/Parallel.cpp
+++ b/llvm/lib/Support/Parallel.cpp
@@ -193,16 +193,7 @@ class ThreadPoolExecutor : public Executor {
JobserverClient *TheJobserver = nullptr;
};
-// A global raw pointer to the executor. Lifetime is managed by the
-// objects created within createExecutor().
-static Executor *TheExec = nullptr;
-static std::once_flag Flag;
-
-// This function will be called exactly once to create the executor.
-// It contains the necessary platform-specific logic. Since functions
-// called by std::call_once cannot return value, we have to set the
-// executor as a global variable.
-void createExecutor() {
+Executor *Executor::getDefaultExecutor() {
#ifdef _WIN32
// The ManagedStatic enables the ThreadPoolExecutor to be stopped via
// llvm_shutdown() which allows a "clean" fast exit, e.g. via _exit(). This
@@ -221,27 +212,19 @@ void createExecutor() {
// are more frequent with the debug static runtime.
//
// This also prevents intermittent deadlocks on exit with the MinGW runtime.
-
static ManagedStatic<ThreadPoolExecutor, ThreadPoolExecutor::Creator,
ThreadPoolExecutor::Deleter>
ManagedExec;
- static std::unique_ptr<ThreadPoolExecutor> Exec(&(*ManagedExec));
- TheExec = Exec.get();
+ return &*ManagedExec;
#else
// ManagedStatic is not desired on other platforms. When `Exec` is destroyed
// by llvm_shutdown(), worker threads will clean up and invoke TLS
// destructors. This can lead to race conditions if other threads attempt to
// access TLS objects that have already been destroyed.
static ThreadPoolExecutor Exec(strategy);
- TheExec = &Exec;
+ return &Exec;
#endif
}
-
-Executor *Executor::getDefaultExecutor() {
- // Use std::call_once to lazily and safely initialize the executor.
- std::call_once(Flag, createExecutor);
- return TheExec;
-}
} // namespace
} // namespace detail
diff --git a/llvm/unittests/Support/JobserverTest.cpp b/llvm/unittests/Support/JobserverTest.cpp
index d27445897db0a..e2da4829c951f 100644
--- a/llvm/unittests/Support/JobserverTest.cpp
+++ b/llvm/unittests/Support/JobserverTest.cpp
@@ -15,6 +15,7 @@
#include "llvm/Config/llvm-config.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Parallel.h"
+#include "llvm/Support/Program.h"
#include "llvm/Support/ThreadPool.h"
#include "llvm/Support/raw_ostream.h"
#include "gtest/gtest.h"
@@ -40,8 +41,14 @@
using namespace llvm;
+// Provided by the unit test main to locate the current test binary.
+extern const char *TestMainArgv0;
+
namespace {
+// Unique anchor whose address helps locate the current test binary.
+static int JobserverTestAnchor = 0;
+
// RAII helper to set an environment variable for the duration of a test.
class ScopedEnvironment {
std::string Name;
@@ -382,18 +389,43 @@ TEST_F(JobserverStrategyTest, ThreadPoolConcurrencyIsLimited) {
EXPECT_EQ(CompletedTasks, NumTasks);
}
-TEST_F(JobserverStrategyTest, ParallelForIsLimited) {
+// Parent-side driver that spawns a fresh process to run the child test which
+// validates that parallelFor respects the jobserver limit when it is the first
+// user of the default executor in that process.
+TEST_F(JobserverStrategyTest, ParallelForIsLimited_Subprocess) {
+ // Mark child execution.
+ setenv("LLVM_JOBSERVER_TEST_CHILD", "1", 1);
+
+ // Find the current test binary and build args to run only the child test.
+ std::string Executable =
+ sys::fs::getMainExecutable(TestMainArgv0, &JobserverTestAnchor);
+ SmallVector<StringRef, 4> Args{Executable,
+ "--gtest_filter=JobserverStrategyTest."
+ "ParallelForIsLimited_SubprocessChild"};
+
+ std::string Error;
+ bool ExecFailed = false;
+ int RC = sys::ExecuteAndWait(Executable, Args, std::nullopt, {}, 0, 0, &Error,
+ &ExecFailed);
+ unsetenv("LLVM_JOBSERVER_TEST_CHILD");
+ ASSERT_FALSE(ExecFailed) << Error;
+ ASSERT_EQ(RC, 0) << Error;
+}
+
+// Child-side test: create FIFO and make-proxy in this process, set the
+// jobserver strategy, and then run parallelFor.
+TEST_F(JobserverStrategyTest, ParallelForIsLimited_SubprocessChild) {
+ if (!getenv("LLVM_JOBSERVER_TEST_CHILD"))
+ GTEST_SKIP() << "Not running in child mode";
+
// This test verifies that llvm::parallelFor respects the jobserver limit.
const int NumExplicitJobs = 3;
const int ConcurrencyLimit = NumExplicitJobs + 1; // +1 implicit
const int NumTasks = 20;
- LLVM_DEBUG(dbgs() << "Calling startMakeProxy with " << NumExplicitJobs
- << " jobs.\n");
startMakeProxy(NumExplicitJobs);
- LLVM_DEBUG(dbgs() << "MakeProxy is running.\n");
- // Set the global strategy. parallelFor will use this.
+ // Set the global strategy before any default executor is created.
parallel::strategy = jobserver_concurrency();
std::atomic<int> ActiveTasks{0};
@@ -401,32 +433,48 @@ TEST_F(JobserverStrategyTest, ParallelForIsLimited) {
parallelFor(0, NumTasks, [&](int i) {
int CurrentActive = ++ActiveTasks;
- LLVM_DEBUG(dbgs() << "Task " << i << ": Active tasks: " << CurrentActive
- << "\n");
+ (void)i;
int OldMax = MaxActiveTasks.load();
while (CurrentActive > OldMax)
MaxActiveTasks.compare_exchange_weak(OldMax, CurrentActive);
-
std::this_thread::sleep_for(std::chrono::milliseconds(20));
--ActiveTasks;
});
- LLVM_DEBUG(dbgs() << "ParallelFor finished. Max active tasks was "
- << MaxActiveTasks << ".\n");
EXPECT_LE(MaxActiveTasks, ConcurrencyLimit);
}
-TEST_F(JobserverStrategyTest, ParallelSortIsLimited) {
- // This test serves as an integration test to ensure parallelSort completes
- // correctly when running under the jobserver strategy. It doesn't directly
- // measure concurrency but verifies correctness.
+// Parent-side driver for parallelSort child test.
+TEST_F(JobserverStrategyTest, ParallelSortIsLimited_Subprocess) {
+ setenv("LLVM_JOBSERVER_TEST_CHILD", "1", 1);
+
+ std::string Executable =
+ sys::fs::getMainExecutable(TestMainArgv0, &JobserverTestAnchor);
+ SmallVector<StringRef, 4> Args{Executable,
+ "--gtest_filter=JobserverStrategyTest."
+ "ParallelSortIsLimited_SubprocessChild"};
+
+ std::string Error;
+ bool ExecFailed = false;
+ int RC = sys::ExecuteAndWait(Executable, Args, std::nullopt, {}, 0, 0, &Error,
+ &ExecFailed);
+ unsetenv("LLVM_JOBSERVER_TEST_CHILD");
+ ASSERT_FALSE(ExecFailed) << Error;
+ ASSERT_EQ(RC, 0) << Error;
+}
+
+// Child-side test: ensure parallelSort runs and completes correctly under the
+// jobserver strategy when it owns default executor initialization.
+TEST_F(JobserverStrategyTest, ParallelSortIsLimited_SubprocessChild) {
+ if (!getenv("LLVM_JOBSERVER_TEST_CHILD"))
+ GTEST_SKIP() << "Not running in child mode";
+
const int NumExplicitJobs = 3;
startMakeProxy(NumExplicitJobs);
parallel::strategy = jobserver_concurrency();
std::vector<int> V(1024);
- // Fill with random data
std::mt19937 randEngine;
std::uniform_int_distribution<int> dist;
for (int &i : V)
``````````
</details>
https://github.com/llvm/llvm-project/pull/165264
More information about the llvm-commits
mailing list